Ether Framework
Unified API docs for Ether modules
Loading...
Searching...
No Matches
JettyServerFactory.java
Go to the documentation of this file.
1package dev.rafex.ether.http.jetty12;
2
3/*-
4 * #%L
5 * ether-http-jetty12
6 * %%
7 * Copyright (C) 2025 - 2026 Raúl Eduardo González Argote
8 * %%
9 * Permission is hereby granted, free of charge, to any person obtaining a copy
10 * of this software and associated documentation files (the "Software"), to deal
11 * in the Software without restriction, including without limitation the rights
12 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 * copies of the Software, and to permit persons to whom the Software is
14 * furnished to do so, subject to the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included in
17 * all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 * THE SOFTWARE.
26 * #L%
27 */
28
29import java.time.Duration;
30import java.util.ArrayList;
31import java.util.List;
32import java.util.Map;
33import java.util.Objects;
34
35import org.eclipse.jetty.http.pathmap.PathSpec;
36import org.eclipse.jetty.server.ForwardedRequestCustomizer;
37import org.eclipse.jetty.server.Handler;
38import org.eclipse.jetty.server.HttpConfiguration;
39import org.eclipse.jetty.server.HttpConnectionFactory;
40import org.eclipse.jetty.server.Server;
41import org.eclipse.jetty.server.ServerConnector;
42import org.eclipse.jetty.server.handler.GracefulHandler;
43import org.eclipse.jetty.server.handler.PathMappingsHandler;
44import org.eclipse.jetty.server.handler.QoSHandler;
45import org.eclipse.jetty.server.handler.SizeLimitHandler;
46import org.eclipse.jetty.server.handler.ThreadLimitHandler;
47import org.eclipse.jetty.util.MultiMap;
48import org.eclipse.jetty.util.UrlEncoded;
49import org.eclipse.jetty.util.thread.QueuedThreadPool;
50import org.eclipse.jetty.websocket.server.ServerUpgradeRequest;
51import org.eclipse.jetty.websocket.server.ServerUpgradeResponse;
52import org.eclipse.jetty.websocket.server.WebSocketUpgradeHandler;
53
54import dev.rafex.ether.http.core.AuthPolicy;
55import dev.rafex.ether.http.jetty12.middleware.JettyMiddleware;
56import dev.rafex.ether.http.jetty12.middleware.JettyMiddlewareRegistry;
57import dev.rafex.ether.http.jetty12.response.JettyApiErrorResponses;
58import dev.rafex.ether.http.jetty12.response.JettyApiResponses;
59import dev.rafex.ether.http.jetty12.routing.JettyRouteRegistration;
60import dev.rafex.ether.http.jetty12.routing.JettyRouteRegistry;
61import dev.rafex.ether.http.jetty12.security.JettyAuthPolicyRegistry;
62import dev.rafex.ether.http.jetty12.security.TokenVerifier;
63import dev.rafex.ether.http.security.profile.HttpSecurityProfile;
64import dev.rafex.ether.http.security.proxy.TrustedProxyPolicy;
65import dev.rafex.ether.json.JsonCodec;
66import dev.rafex.ether.observability.core.request.RequestIdGenerator;
67import dev.rafex.ether.observability.core.request.UuidRequestIdGenerator;
68import dev.rafex.ether.observability.core.timing.TimingRecorder;
69import dev.rafex.ether.websocket.core.WebSocketRoute;
70
71public final class JettyServerFactory {
72
73 private static final TimingRecorder NOOP_TIMING_RECORDER = sample -> {
74 };
75
76 private JettyServerFactory() {
77 }
78
79 public static JettyServerRunner create(final JettyServerConfig config, final JettyRouteRegistry routeRegistry,
80 final JsonCodec jsonCodec) {
81 return create(config, routeRegistry, jsonCodec, null, List.of(), List.of(), List.of(),
82 HttpSecurityProfile.defaults(), new UuidRequestIdGenerator(), NOOP_TIMING_RECORDER);
83 }
84
85 public static JettyServerRunner create(final JettyServerConfig config, final JettyRouteRegistry routeRegistry,
86 final JsonCodec jsonCodec, final TokenVerifier tokenVerifier, final List<AuthPolicy> authPolicies,
87 final List<JettyMiddleware> middlewares) {
88 return create(config, routeRegistry, jsonCodec, tokenVerifier, authPolicies, middlewares, List.of(),
89 HttpSecurityProfile.defaults(), new UuidRequestIdGenerator(), NOOP_TIMING_RECORDER);
90 }
91
92 public static JettyServerRunner create(final JettyServerConfig config, final JettyRouteRegistry routeRegistry,
93 final JsonCodec jsonCodec, final TokenVerifier tokenVerifier, final List<AuthPolicy> authPolicies,
94 final List<JettyMiddleware> middlewares, final List<WebSocketRoute> wsRoutes,
95 final HttpSecurityProfile securityProfile,
96 final RequestIdGenerator requestIdGenerator, final TimingRecorder timingRecorder) {
97
98 Objects.requireNonNull(config, "config");
99 Objects.requireNonNull(routeRegistry, "routeRegistry");
100 Objects.requireNonNull(jsonCodec, "jsonCodec");
101 registerBuiltinRoutes(routeRegistry, config, jsonCodec, tokenVerifier, securityProfile, requestIdGenerator,
102 timingRecorder);
103
104 final var pool = new QueuedThreadPool();
105 pool.setMaxThreads(config.maxThreads());
106 pool.setMinThreads(config.minThreads());
107 pool.setIdleTimeout(config.idleTimeoutMs());
108 pool.setName(config.threadPoolName());
109
110 final var server = new Server(pool);
111 server.setStopAtShutdown(config.stopAtShutdown());
112 server.setStopTimeout(config.stopTimeoutMs());
113
114 final var httpConfig = buildHttpConfiguration(config, securityProfile);
115 final var connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
116 if (config.host() != null && !config.host().isBlank()) {
117 connector.setHost(config.host());
118 }
119 connector.setPort(config.port());
120 connector.setIdleTimeout(config.idleTimeoutMs());
121 connector.setAcceptQueueSize(config.acceptQueueSize());
122 connector.setReuseAddress(config.reuseAddress());
123 server.addConnector(connector);
124
125 final var routesHandler = buildRoutes(routeRegistry.routes());
126 final var withWebSockets = buildWebSocketHandler(server, routesHandler, wsRoutes);
127 final var withAuth = applyAuthPolicies(withWebSockets, tokenVerifier, jsonCodec, authPolicies);
128 final var withMiddlewares = applyMiddlewares(withAuth,
129 mergeMiddlewares(jsonCodec, securityProfile, requestIdGenerator, timingRecorder, middlewares));
130 final var withSizeLimits = applySizeLimits(withMiddlewares, config);
131 final var withQoS = applyQoS(withSizeLimits, config, securityProfile);
132 final var withThreadLimit = applyThreadLimit(withQoS, config);
133 final var appHandler = applyGracefulShutdown(withThreadLimit, config);
134 server.setHandler(appHandler);
135
136 return new JettyServerRunner(server);
137 }
138
139 private static void registerBuiltinRoutes(final JettyRouteRegistry routeRegistry, final JettyServerConfig config,
140 final JsonCodec jsonCodec, final TokenVerifier tokenVerifier, final HttpSecurityProfile securityProfile,
141 final RequestIdGenerator requestIdGenerator, final TimingRecorder timingRecorder) {
142 final var context = new JettyModuleContext(config, jsonCodec, tokenVerifier, securityProfile,
143 requestIdGenerator, timingRecorder);
144 JettyBuiltinModule.registerRoutes(routeRegistry, context);
145 }
146
147 public static JettyServerRunner create(final JettyServerConfig config, final JsonCodec jsonCodec,
148 final TokenVerifier tokenVerifier, final List<JettyModule> modules) {
149 return create(config, jsonCodec, tokenVerifier, modules, HttpSecurityProfile.defaults(),
150 new UuidRequestIdGenerator(), NOOP_TIMING_RECORDER);
151 }
152
153 public static JettyServerRunner create(final JettyServerConfig config, final JsonCodec jsonCodec,
154 final TokenVerifier tokenVerifier, final List<JettyModule> modules,
155 final HttpSecurityProfile securityProfile, final RequestIdGenerator requestIdGenerator,
156 final TimingRecorder timingRecorder) {
157 final var routeRegistry = new JettyRouteRegistry();
158 final var authPolicyRegistry = new JettyAuthPolicyRegistry();
159 final var middlewareRegistry = new JettyMiddlewareRegistry();
160 final var wsRoutes = new ArrayList<WebSocketRoute>();
161
162 final var context = new JettyModuleContext(config, jsonCodec, tokenVerifier, securityProfile,
163 requestIdGenerator, timingRecorder);
164 for (final var module : modules == null ? List.<JettyModule>of() : modules) {
165 module.registerRoutes(routeRegistry, context);
166 module.registerAuthPolicies(authPolicyRegistry, context);
167 module.registerMiddlewares(middlewareRegistry, context);
168 module.registerWebSocketRoutes(wsRoutes, context);
169 }
170 JettyBuiltinModule.registerRoutes(routeRegistry, context);
171
172 return create(config, routeRegistry, jsonCodec, tokenVerifier, authPolicyRegistry.policies(),
173 middlewareRegistry.middlewares(), List.copyOf(wsRoutes), securityProfile, requestIdGenerator,
174 timingRecorder);
175 }
176
177 private static PathMappingsHandler buildRoutes(final List<JettyRouteRegistration> routes) {
178 final var handler = new PathMappingsHandler();
179 for (final var route : routes) {
180 handler.addMapping(PathSpec.from(route.pathSpec()), route.handler());
181 }
182 return handler;
183 }
184
185 private static Handler buildWebSocketHandler(final Server server, final PathMappingsHandler httpHandler,
186 final List<WebSocketRoute> wsRoutes) {
187 if (wsRoutes == null || wsRoutes.isEmpty()) {
188 return httpHandler;
189 }
190
191 final var wsHandler = WebSocketUpgradeHandler.from(server, container -> {
192 for (final var route : wsRoutes) {
193 container.addMapping(PathSpec.from(route.pattern()),
194 (request, response, callback) -> createEndpoint(route, request, response));
195 }
196 });
197 wsHandler.setHandler(httpHandler);
198 return wsHandler;
199 }
200
201 private static Object createEndpoint(final WebSocketRoute route, final ServerUpgradeRequest request,
202 final ServerUpgradeResponse response) {
203 return dev.rafex.ether.websocket.jetty12.JettyWebSocketUpgrades.createEndpoint(route, request, response);
204 }
205
206 private static Handler applyAuthPolicies(final Handler delegate, final TokenVerifier tokenVerifier,
207 final JsonCodec jsonCodec, final List<AuthPolicy> policies) {
208 if (tokenVerifier == null || policies == null || policies.isEmpty()) {
209 return delegate;
210 }
211 return new JettyAuthHandler(delegate, tokenVerifier, jsonCodec).authPolicies(policies);
212 }
213
214 private static Handler applyMiddlewares(final Handler delegate, final List<JettyMiddleware> middlewares) {
215 var current = delegate;
216 final var stack = middlewares == null ? List.<JettyMiddleware>of() : new ArrayList<>(middlewares);
217 for (int i = stack.size() - 1; i >= 0; i--) {
218 current = stack.get(i).wrap(current);
219 }
220 return current;
221 }
222
223 private static List<JettyMiddleware> mergeMiddlewares(final JsonCodec jsonCodec,
224 final HttpSecurityProfile securityProfile, final RequestIdGenerator requestIdGenerator,
225 final TimingRecorder timingRecorder, final List<JettyMiddleware> customMiddlewares) {
226 final var merged = new ArrayList<JettyMiddleware>();
227 merged.addAll(defaultMiddlewares(jsonCodec, securityProfile, requestIdGenerator, timingRecorder));
228 if (customMiddlewares != null) {
229 merged.addAll(customMiddlewares);
230 }
231 return List.copyOf(merged);
232 }
233
234 private static List<JettyMiddleware> defaultMiddlewares(final JsonCodec jsonCodec,
235 final HttpSecurityProfile securityProfile, final RequestIdGenerator requestIdGenerator,
236 final TimingRecorder timingRecorder) {
237 final var profile = securityProfile == null ? HttpSecurityProfile.defaults() : securityProfile;
238 final var requestIds = requestIdGenerator == null ? new UuidRequestIdGenerator() : requestIdGenerator;
239 final var timings = timingRecorder == null ? NOOP_TIMING_RECORDER : timingRecorder;
240
241 final var defaults = new ArrayList<JettyMiddleware>();
242 defaults.add(next -> new JettyObservabilityHandler(next, requestIds, timings));
243 if (profile.rateLimit() != null && profile.rateLimit().isEnabled()) {
244 defaults.add(next -> new JettyRateLimitHandler(next, profile.rateLimit(), profile.trustedProxies(),
245 new JettyApiErrorResponses(jsonCodec)));
246 }
247 defaults.add(next -> new JettyIpPolicyHandler(next, profile.ipPolicy(), profile.trustedProxies(),
248 new JettyApiErrorResponses(jsonCodec)));
249 defaults.add(next -> new JettyCorsHandler(next, profile.cors(), new JettyApiResponses(jsonCodec)));
250 defaults.add(next -> new JettySecurityHeadersHandler(next, profile.headers()));
251 return defaults;
252 }
253
254 private static HttpConfiguration buildHttpConfiguration(final JettyServerConfig config,
255 final HttpSecurityProfile securityProfile) {
256 final var httpConfig = new HttpConfiguration();
257 httpConfig.setInputBufferSize(config.inputBufferSize());
258 httpConfig.setOutputBufferSize(config.outputBufferSize());
259 httpConfig.setRequestHeaderSize(config.requestHeaderSize());
260 httpConfig.setResponseHeaderSize(config.responseHeaderSize());
261 httpConfig.setMinRequestDataRate(config.minRequestDataRate());
262 httpConfig.setMinResponseDataRate(config.minResponseDataRate());
263 httpConfig.setMaxErrorDispatches(config.maxErrorDispatches());
264 httpConfig.setMaxUnconsumedRequestContentReads(config.maxUnconsumedRequestContentReads());
265 httpConfig.setSendServerVersion(false);
266 httpConfig.setSendDateHeader(true);
267 httpConfig.setRelativeRedirectAllowed(false);
268 httpConfig.setNotifyForbiddenComplianceViolations(true);
269 httpConfig.setPersistentConnectionsEnabled(true);
270 httpConfig.setSecureScheme("https");
271 httpConfig.setSecurePort(443);
272
273 if (trustForwardHeaders(config, securityProfile)) {
274 final var forwarded = new ForwardedRequestCustomizer();
275 forwarded.setForwardedOnly(forwardedOnly(config, securityProfile));
276 httpConfig.addCustomizer(forwarded);
277 }
278
279 return httpConfig;
280 }
281
282 private static Handler applySizeLimits(final Handler delegate, final JettyServerConfig config) {
283 final var requestLimit = normalizeLimit(config.maxRequestBodyBytes());
284 final var responseLimit = normalizeLimit(config.maxResponseBodyBytes());
285 if (requestLimit < 0 && responseLimit < 0) {
286 return delegate;
287 }
288
289 final var handler = new SizeLimitHandler(requestLimit, responseLimit);
290 handler.setHandler(delegate);
291 return handler;
292 }
293
294 private static Handler applyQoS(final Handler delegate, final JettyServerConfig config,
295 final HttpSecurityProfile securityProfile) {
296 final int effectiveMaxRequests = Math.max(config.maxConcurrentRequests(),
297 maxConcurrentRequests(securityProfile));
298 if (effectiveMaxRequests <= 0) {
299 return delegate;
300 }
301
302 final var handler = new QoSHandler();
303 handler.setMaxRequestCount(effectiveMaxRequests);
304 handler.setMaxSuspendedRequestCount(config.maxSuspendedRequests());
305 handler.setMaxSuspend(Duration.ofMillis(Math.max(0L, config.maxSuspendMs())));
306 handler.setHandler(delegate);
307 return handler;
308 }
309
310 private static Handler applyThreadLimit(final Handler delegate, final JettyServerConfig config) {
311 if (config.maxRequestsPerRemoteIp() <= 0) {
312 return delegate;
313 }
314
315 final String forwardedHeader;
316 final boolean rfc7239;
317 if (config.trustForwardHeaders()) {
318 forwardedHeader = config.forwardedOnly() ? "Forwarded" : "X-Forwarded-For";
319 rfc7239 = config.forwardedOnly();
320 } else {
321 forwardedHeader = null;
322 rfc7239 = true;
323 }
324
325 final var handler = new ThreadLimitHandler(forwardedHeader, rfc7239);
326 handler.setThreadLimit(config.maxRequestsPerRemoteIp());
327 handler.setHandler(delegate);
328 return handler;
329 }
330
331 private static Handler applyGracefulShutdown(final Handler delegate, final JettyServerConfig config) {
332 final var handler = new GracefulHandler();
333 handler.setShutdownIdleTimeout(Math.max(0L, config.shutdownIdleTimeoutMs()));
334 handler.setHandler(delegate);
335 return handler;
336 }
337
338 private static long normalizeLimit(final long value) {
339 return value <= 0 ? -1L : value;
340 }
341
342 private static int maxConcurrentRequests(final HttpSecurityProfile securityProfile) {
343 if (securityProfile == null || securityProfile.rateLimit() == null) {
344 return 0;
345 }
346 return Math.max(0, securityProfile.rateLimit().maxConcurrentRequests());
347 }
348
349 private static boolean trustForwardHeaders(final JettyServerConfig config,
350 final HttpSecurityProfile securityProfile) {
351 if (config.trustForwardHeaders()) {
352 return true;
353 }
354 return trustedProxyPolicy(securityProfile).trustForwardedHeader();
355 }
356
357 private static boolean forwardedOnly(final JettyServerConfig config, final HttpSecurityProfile securityProfile) {
358 if (config.trustForwardHeaders()) {
359 return config.forwardedOnly();
360 }
361 return trustedProxyPolicy(securityProfile).forwardedOnly();
362 }
363
364 private static TrustedProxyPolicy trustedProxyPolicy(final HttpSecurityProfile securityProfile) {
365 if (securityProfile == null || securityProfile.trustedProxies() == null) {
366 return TrustedProxyPolicy.disabled();
367 }
368 return securityProfile.trustedProxies();
369 }
370}
static JettyServerRunner create(final JettyServerConfig config, final JsonCodec jsonCodec, final TokenVerifier tokenVerifier, final List< JettyModule > modules, final HttpSecurityProfile securityProfile, final RequestIdGenerator requestIdGenerator, final TimingRecorder timingRecorder)
static JettyServerRunner create(final JettyServerConfig config, final JettyRouteRegistry routeRegistry, final JsonCodec jsonCodec)
static JettyServerRunner create(final JettyServerConfig config, final JettyRouteRegistry routeRegistry, final JsonCodec jsonCodec, final TokenVerifier tokenVerifier, final List< AuthPolicy > authPolicies, final List< JettyMiddleware > middlewares)
static JettyServerRunner create(final JettyServerConfig config, final JsonCodec jsonCodec, final TokenVerifier tokenVerifier, final List< JettyModule > modules)
static JettyServerRunner create(final JettyServerConfig config, final JettyRouteRegistry routeRegistry, final JsonCodec jsonCodec, final TokenVerifier tokenVerifier, final List< AuthPolicy > authPolicies, final List< JettyMiddleware > middlewares, final List< WebSocketRoute > wsRoutes, final HttpSecurityProfile securityProfile, final RequestIdGenerator requestIdGenerator, final TimingRecorder timingRecorder)
Typed configuration entry points and composition APIs for Ether applications.
record JettyServerConfig(String host, int port, int maxThreads, int minThreads, int idleTimeoutMs, String threadPoolName, String environment, int acceptQueueSize, boolean reuseAddress, boolean stopAtShutdown, long stopTimeoutMs, long shutdownIdleTimeoutMs, boolean trustForwardHeaders, boolean forwardedOnly, int inputBufferSize, int outputBufferSize, int requestHeaderSize, int responseHeaderSize, long minRequestDataRate, long minResponseDataRate, int maxErrorDispatches, int maxUnconsumedRequestContentReads, long maxRequestBodyBytes, long maxResponseBodyBytes, int maxConcurrentRequests, int maxSuspendedRequests, long maxSuspendMs, int maxRequestsPerRemoteIp)
record JettyModuleContext(JettyServerConfig config, JsonCodec jsonCodec, TokenVerifier tokenVerifier, HttpSecurityProfile securityProfile, RequestIdGenerator requestIdGenerator, TimingRecorder timingRecorder)
record HttpSecurityProfile(CorsPolicy cors, SecurityHeadersPolicy headers, TrustedProxyPolicy trustedProxies, IpPolicy ipPolicy, RateLimitPolicy rateLimit)
Perfil de seguridad HTTP que agrupa todas las políticas de seguridad.
record TrustedProxyPolicy(List< String > trustedSources, boolean trustForwardedHeader, boolean forwardedOnly, boolean preferRightMostForwardedFor)
Política para configurar proxy de confianza en servidores.