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.Objects;
33
34import org.eclipse.jetty.http.pathmap.PathSpec;
35import org.eclipse.jetty.server.ForwardedRequestCustomizer;
36import org.eclipse.jetty.server.Handler;
37import org.eclipse.jetty.server.HttpConfiguration;
38import org.eclipse.jetty.server.HttpConnectionFactory;
39import org.eclipse.jetty.server.Server;
40import org.eclipse.jetty.server.ServerConnector;
41import org.eclipse.jetty.server.handler.GracefulHandler;
42import org.eclipse.jetty.server.handler.PathMappingsHandler;
43import org.eclipse.jetty.server.handler.QoSHandler;
44import org.eclipse.jetty.server.handler.SizeLimitHandler;
45import org.eclipse.jetty.server.handler.ThreadLimitHandler;
46import org.eclipse.jetty.util.thread.QueuedThreadPool;
47
48import dev.rafex.ether.http.core.AuthPolicy;
49import dev.rafex.ether.http.jetty12.middleware.JettyMiddleware;
50import dev.rafex.ether.http.jetty12.middleware.JettyMiddlewareRegistry;
51import dev.rafex.ether.http.jetty12.response.JettyApiErrorResponses;
52import dev.rafex.ether.http.jetty12.response.JettyApiResponses;
53import dev.rafex.ether.http.jetty12.routing.JettyRouteRegistration;
54import dev.rafex.ether.http.jetty12.routing.JettyRouteRegistry;
55import dev.rafex.ether.http.jetty12.security.JettyAuthPolicyRegistry;
56import dev.rafex.ether.http.jetty12.security.TokenVerifier;
57import dev.rafex.ether.http.security.profile.HttpSecurityProfile;
58import dev.rafex.ether.http.security.proxy.TrustedProxyPolicy;
59import dev.rafex.ether.json.JsonCodec;
60import dev.rafex.ether.observability.core.request.RequestIdGenerator;
61import dev.rafex.ether.observability.core.request.UuidRequestIdGenerator;
62import dev.rafex.ether.observability.core.timing.TimingRecorder;
63
64public final class JettyServerFactory {
65
66 private static final TimingRecorder NOOP_TIMING_RECORDER = sample -> {
67 };
68
69 private JettyServerFactory() {
70 }
71
72 public static JettyServerRunner create(final JettyServerConfig config, final JettyRouteRegistry routeRegistry,
73 final JsonCodec jsonCodec) {
74 return create(config, routeRegistry, jsonCodec, null, List.of(), List.of(), HttpSecurityProfile.defaults(),
75 new UuidRequestIdGenerator(), NOOP_TIMING_RECORDER);
76 }
77
78 public static JettyServerRunner create(final JettyServerConfig config, final JettyRouteRegistry routeRegistry,
79 final JsonCodec jsonCodec, final TokenVerifier tokenVerifier, final List<AuthPolicy> authPolicies,
80 final List<JettyMiddleware> middlewares) {
81 return create(config, routeRegistry, jsonCodec, tokenVerifier, authPolicies, middlewares,
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, final HttpSecurityProfile securityProfile,
88 final RequestIdGenerator requestIdGenerator, final TimingRecorder timingRecorder) {
89
90 Objects.requireNonNull(config, "config");
91 Objects.requireNonNull(routeRegistry, "routeRegistry");
92 Objects.requireNonNull(jsonCodec, "jsonCodec");
93 registerBuiltinRoutes(routeRegistry, config, jsonCodec, tokenVerifier, securityProfile, requestIdGenerator,
94 timingRecorder);
95
96 final var pool = new QueuedThreadPool();
97 pool.setMaxThreads(config.maxThreads());
98 pool.setMinThreads(config.minThreads());
99 pool.setIdleTimeout(config.idleTimeoutMs());
100 pool.setName(config.threadPoolName());
101
102 final var server = new Server(pool);
103 server.setStopAtShutdown(config.stopAtShutdown());
104 server.setStopTimeout(config.stopTimeoutMs());
105
106 final var httpConfig = buildHttpConfiguration(config, securityProfile);
107 final var connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
108 if (config.host() != null && !config.host().isBlank()) {
109 connector.setHost(config.host());
110 }
111 connector.setPort(config.port());
112 connector.setIdleTimeout(config.idleTimeoutMs());
113 connector.setAcceptQueueSize(config.acceptQueueSize());
114 connector.setReuseAddress(config.reuseAddress());
115 server.addConnector(connector);
116
117 final var routesHandler = buildRoutes(routeRegistry.routes());
118 final var withAuth = applyAuthPolicies(routesHandler, tokenVerifier, jsonCodec, authPolicies);
119 final var withMiddlewares = applyMiddlewares(withAuth,
120 mergeMiddlewares(jsonCodec, securityProfile, requestIdGenerator, timingRecorder, middlewares));
121 final var withSizeLimits = applySizeLimits(withMiddlewares, config);
122 final var withQoS = applyQoS(withSizeLimits, config, securityProfile);
123 final var withThreadLimit = applyThreadLimit(withQoS, config);
124 final var appHandler = applyGracefulShutdown(withThreadLimit, config);
125 server.setHandler(appHandler);
126
127 return new JettyServerRunner(server);
128 }
129
130 private static void registerBuiltinRoutes(final JettyRouteRegistry routeRegistry, final JettyServerConfig config,
131 final JsonCodec jsonCodec, final TokenVerifier tokenVerifier, final HttpSecurityProfile securityProfile,
132 final RequestIdGenerator requestIdGenerator, final TimingRecorder timingRecorder) {
133 final var context = new JettyModuleContext(config, jsonCodec, tokenVerifier, securityProfile,
134 requestIdGenerator, timingRecorder);
135 JettyBuiltinModule.registerRoutes(routeRegistry, context);
136 }
137
138 public static JettyServerRunner create(final JettyServerConfig config, final JsonCodec jsonCodec,
139 final TokenVerifier tokenVerifier, final List<JettyModule> modules) {
140 return create(config, jsonCodec, tokenVerifier, modules, HttpSecurityProfile.defaults(),
141 new UuidRequestIdGenerator(), NOOP_TIMING_RECORDER);
142 }
143
144 public static JettyServerRunner create(final JettyServerConfig config, final JsonCodec jsonCodec,
145 final TokenVerifier tokenVerifier, final List<JettyModule> modules,
146 final HttpSecurityProfile securityProfile, final RequestIdGenerator requestIdGenerator,
147 final TimingRecorder timingRecorder) {
148 final var routeRegistry = new JettyRouteRegistry();
149 final var authPolicyRegistry = new JettyAuthPolicyRegistry();
150 final var middlewareRegistry = new JettyMiddlewareRegistry();
151
152 final var context = new JettyModuleContext(config, jsonCodec, tokenVerifier, securityProfile,
153 requestIdGenerator, timingRecorder);
154 for (final var module : modules == null ? List.<JettyModule>of() : modules) {
155 module.registerRoutes(routeRegistry, context);
156 module.registerAuthPolicies(authPolicyRegistry, context);
157 module.registerMiddlewares(middlewareRegistry, context);
158 }
159 JettyBuiltinModule.registerRoutes(routeRegistry, context);
160
161 return create(config, routeRegistry, jsonCodec, tokenVerifier, authPolicyRegistry.policies(),
162 middlewareRegistry.middlewares(), securityProfile, requestIdGenerator, timingRecorder);
163 }
164
165 private static PathMappingsHandler buildRoutes(final List<JettyRouteRegistration> routes) {
166 final var handler = new PathMappingsHandler();
167 for (final var route : routes) {
168 handler.addMapping(PathSpec.from(route.pathSpec()), route.handler());
169 }
170 return handler;
171 }
172
173 private static Handler applyAuthPolicies(final Handler delegate, final TokenVerifier tokenVerifier,
174 final JsonCodec jsonCodec, final List<AuthPolicy> policies) {
175 if (tokenVerifier == null || policies == null || policies.isEmpty()) {
176 return delegate;
177 }
178 return new JettyAuthHandler(delegate, tokenVerifier, jsonCodec).authPolicies(policies);
179 }
180
181 private static Handler applyMiddlewares(final Handler delegate, final List<JettyMiddleware> middlewares) {
182 var current = delegate;
183 final var stack = middlewares == null ? List.<JettyMiddleware>of() : new ArrayList<>(middlewares);
184 for (int i = stack.size() - 1; i >= 0; i--) {
185 current = stack.get(i).wrap(current);
186 }
187 return current;
188 }
189
190 private static List<JettyMiddleware> mergeMiddlewares(final JsonCodec jsonCodec,
191 final HttpSecurityProfile securityProfile, final RequestIdGenerator requestIdGenerator,
192 final TimingRecorder timingRecorder, final List<JettyMiddleware> customMiddlewares) {
193 final var merged = new ArrayList<JettyMiddleware>();
194 merged.addAll(defaultMiddlewares(jsonCodec, securityProfile, requestIdGenerator, timingRecorder));
195 if (customMiddlewares != null) {
196 merged.addAll(customMiddlewares);
197 }
198 return List.copyOf(merged);
199 }
200
201 private static List<JettyMiddleware> defaultMiddlewares(final JsonCodec jsonCodec,
202 final HttpSecurityProfile securityProfile, final RequestIdGenerator requestIdGenerator,
203 final TimingRecorder timingRecorder) {
204 final var profile = securityProfile == null ? HttpSecurityProfile.defaults() : securityProfile;
205 final var requestIds = requestIdGenerator == null ? new UuidRequestIdGenerator() : requestIdGenerator;
206 final var timings = timingRecorder == null ? NOOP_TIMING_RECORDER : timingRecorder;
207
208 final var defaults = new ArrayList<JettyMiddleware>();
209 defaults.add(next -> new JettyObservabilityHandler(next, requestIds, timings));
210 if (profile.rateLimit() != null && profile.rateLimit().isEnabled()) {
211 defaults.add(next -> new JettyRateLimitHandler(next, profile.rateLimit(), profile.trustedProxies(),
212 new JettyApiErrorResponses(jsonCodec)));
213 }
214 defaults.add(next -> new JettyIpPolicyHandler(next, profile.ipPolicy(), profile.trustedProxies(),
215 new JettyApiErrorResponses(jsonCodec)));
216 defaults.add(next -> new JettyCorsHandler(next, profile.cors(), new JettyApiResponses(jsonCodec)));
217 defaults.add(next -> new JettySecurityHeadersHandler(next, profile.headers()));
218 return defaults;
219 }
220
221 private static HttpConfiguration buildHttpConfiguration(final JettyServerConfig config,
222 final HttpSecurityProfile securityProfile) {
223 final var httpConfig = new HttpConfiguration();
224 httpConfig.setInputBufferSize(config.inputBufferSize());
225 httpConfig.setOutputBufferSize(config.outputBufferSize());
226 httpConfig.setRequestHeaderSize(config.requestHeaderSize());
227 httpConfig.setResponseHeaderSize(config.responseHeaderSize());
228 httpConfig.setMinRequestDataRate(config.minRequestDataRate());
229 httpConfig.setMinResponseDataRate(config.minResponseDataRate());
230 httpConfig.setMaxErrorDispatches(config.maxErrorDispatches());
231 httpConfig.setMaxUnconsumedRequestContentReads(config.maxUnconsumedRequestContentReads());
232 httpConfig.setSendServerVersion(false);
233 httpConfig.setSendDateHeader(true);
234 httpConfig.setRelativeRedirectAllowed(false);
235 httpConfig.setNotifyForbiddenComplianceViolations(true);
236 httpConfig.setPersistentConnectionsEnabled(true);
237 httpConfig.setSecureScheme("https");
238 httpConfig.setSecurePort(443);
239
240 if (trustForwardHeaders(config, securityProfile)) {
241 final var forwarded = new ForwardedRequestCustomizer();
242 forwarded.setForwardedOnly(forwardedOnly(config, securityProfile));
243 httpConfig.addCustomizer(forwarded);
244 }
245
246 return httpConfig;
247 }
248
249 private static Handler applySizeLimits(final Handler delegate, final JettyServerConfig config) {
250 final var requestLimit = normalizeLimit(config.maxRequestBodyBytes());
251 final var responseLimit = normalizeLimit(config.maxResponseBodyBytes());
252 if (requestLimit < 0 && responseLimit < 0) {
253 return delegate;
254 }
255
256 final var handler = new SizeLimitHandler(requestLimit, responseLimit);
257 handler.setHandler(delegate);
258 return handler;
259 }
260
261 private static Handler applyQoS(final Handler delegate, final JettyServerConfig config,
262 final HttpSecurityProfile securityProfile) {
263 final int effectiveMaxRequests = Math.max(config.maxConcurrentRequests(),
264 maxConcurrentRequests(securityProfile));
265 if (effectiveMaxRequests <= 0) {
266 return delegate;
267 }
268
269 final var handler = new QoSHandler();
270 handler.setMaxRequestCount(effectiveMaxRequests);
271 handler.setMaxSuspendedRequestCount(config.maxSuspendedRequests());
272 handler.setMaxSuspend(Duration.ofMillis(Math.max(0L, config.maxSuspendMs())));
273 handler.setHandler(delegate);
274 return handler;
275 }
276
277 private static Handler applyThreadLimit(final Handler delegate, final JettyServerConfig config) {
278 if (config.maxRequestsPerRemoteIp() <= 0) {
279 return delegate;
280 }
281
282 final String forwardedHeader;
283 final boolean rfc7239;
284 if (config.trustForwardHeaders()) {
285 forwardedHeader = config.forwardedOnly() ? "Forwarded" : "X-Forwarded-For";
286 rfc7239 = config.forwardedOnly();
287 } else {
288 forwardedHeader = null;
289 rfc7239 = true;
290 }
291
292 final var handler = new ThreadLimitHandler(forwardedHeader, rfc7239);
293 handler.setThreadLimit(config.maxRequestsPerRemoteIp());
294 handler.setHandler(delegate);
295 return handler;
296 }
297
298 private static Handler applyGracefulShutdown(final Handler delegate, final JettyServerConfig config) {
299 final var handler = new GracefulHandler();
300 handler.setShutdownIdleTimeout(Math.max(0L, config.shutdownIdleTimeoutMs()));
301 handler.setHandler(delegate);
302 return handler;
303 }
304
305 private static long normalizeLimit(final long value) {
306 return value <= 0 ? -1L : value;
307 }
308
309 private static int maxConcurrentRequests(final HttpSecurityProfile securityProfile) {
310 if (securityProfile == null || securityProfile.rateLimit() == null) {
311 return 0;
312 }
313 return Math.max(0, securityProfile.rateLimit().maxConcurrentRequests());
314 }
315
316 private static boolean trustForwardHeaders(final JettyServerConfig config,
317 final HttpSecurityProfile securityProfile) {
318 if (config.trustForwardHeaders()) {
319 return true;
320 }
321 return trustedProxyPolicy(securityProfile).trustForwardedHeader();
322 }
323
324 private static boolean forwardedOnly(final JettyServerConfig config, final HttpSecurityProfile securityProfile) {
325 if (config.trustForwardHeaders()) {
326 return config.forwardedOnly();
327 }
328 return trustedProxyPolicy(securityProfile).forwardedOnly();
329 }
330
331 private static TrustedProxyPolicy trustedProxyPolicy(final HttpSecurityProfile securityProfile) {
332 if (securityProfile == null || securityProfile.trustedProxies() == null) {
333 return TrustedProxyPolicy.disabled();
334 }
335 return securityProfile.trustedProxies();
336 }
337}
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 JettyRouteRegistry routeRegistry, final JsonCodec jsonCodec, final TokenVerifier tokenVerifier, final List< AuthPolicy > authPolicies, final List< JettyMiddleware > middlewares, final HttpSecurityProfile securityProfile, final RequestIdGenerator requestIdGenerator, final TimingRecorder timingRecorder)
static JettyServerRunner create(final JettyServerConfig config, final JsonCodec jsonCodec, final TokenVerifier tokenVerifier, final List< JettyModule > modules)
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.