71public final class JettyServerFactory {
73 private static final TimingRecorder NOOP_TIMING_RECORDER = sample -> {
76 private JettyServerFactory() {
81 return create(
config, routeRegistry, jsonCodec,
null, List.of(), List.of(), List.of(),
87 final List<JettyMiddleware> middlewares) {
88 return create(
config, routeRegistry, jsonCodec, tokenVerifier, authPolicies, middlewares, List.of(),
94 final List<JettyMiddleware> middlewares,
final List<WebSocketRoute> wsRoutes,
95 final HttpSecurityProfile securityProfile,
98 Objects.requireNonNull(
config,
"config");
99 Objects.requireNonNull(routeRegistry,
"routeRegistry");
100 Objects.requireNonNull(jsonCodec,
"jsonCodec");
101 registerBuiltinRoutes(routeRegistry,
config, jsonCodec, tokenVerifier, securityProfile, requestIdGenerator,
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());
110 final var server =
new Server(pool);
111 server.setStopAtShutdown(
config.stopAtShutdown());
112 server.setStopTimeout(
config.stopTimeoutMs());
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());
119 connector.setPort(
config.port());
120 connector.setIdleTimeout(
config.idleTimeoutMs());
121 connector.setAcceptQueueSize(
config.acceptQueueSize());
122 connector.setReuseAddress(
config.reuseAddress());
123 server.addConnector(connector);
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);
143 requestIdGenerator, timingRecorder);
144 JettyBuiltinModule.registerRoutes(routeRegistry, context);
148 final TokenVerifier tokenVerifier,
final List<JettyModule> modules) {
149 return create(
config, jsonCodec, tokenVerifier, modules, HttpSecurityProfile.defaults(),
154 final TokenVerifier tokenVerifier,
final List<JettyModule> modules,
155 final HttpSecurityProfile securityProfile,
final RequestIdGenerator requestIdGenerator,
160 final var wsRoutes =
new ArrayList<WebSocketRoute>();
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);
170 JettyBuiltinModule.registerRoutes(routeRegistry, context);
172 return create(
config, routeRegistry, jsonCodec, tokenVerifier, authPolicyRegistry.policies(),
173 middlewareRegistry.middlewares(), List.copyOf(wsRoutes), securityProfile, requestIdGenerator,
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());
185 private static Handler buildWebSocketHandler(
final Server server,
final PathMappingsHandler httpHandler,
186 final List<WebSocketRoute> wsRoutes) {
187 if (wsRoutes ==
null || wsRoutes.isEmpty()) {
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));
197 wsHandler.setHandler(httpHandler);
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);
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()) {
211 return new JettyAuthHandler(delegate, tokenVerifier, jsonCodec).authPolicies(policies);
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);
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);
231 return List.copyOf(merged);
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;
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)));
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()));
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);
273 if (trustForwardHeaders(config, securityProfile)) {
274 final var forwarded =
new ForwardedRequestCustomizer();
275 forwarded.setForwardedOnly(forwardedOnly(config, securityProfile));
276 httpConfig.addCustomizer(forwarded);
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) {
289 final var handler =
new SizeLimitHandler(requestLimit, responseLimit);
290 handler.setHandler(delegate);
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) {
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);
310 private static Handler applyThreadLimit(
final Handler delegate,
final JettyServerConfig config) {
311 if (config.maxRequestsPerRemoteIp() <= 0) {
315 final String forwardedHeader;
316 final boolean rfc7239;
317 if (config.trustForwardHeaders()) {
318 forwardedHeader = config.forwardedOnly() ?
"Forwarded" :
"X-Forwarded-For";
319 rfc7239 = config.forwardedOnly();
321 forwardedHeader =
null;
325 final var handler =
new ThreadLimitHandler(forwardedHeader, rfc7239);
326 handler.setThreadLimit(config.maxRequestsPerRemoteIp());
327 handler.setHandler(delegate);
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);
338 private static long normalizeLimit(
final long value) {
339 return value <= 0 ? -1L : value;
342 private static int maxConcurrentRequests(
final HttpSecurityProfile securityProfile) {
343 if (securityProfile ==
null || securityProfile.rateLimit() ==
null) {
346 return Math.max(0, securityProfile.rateLimit().maxConcurrentRequests());
350 final HttpSecurityProfile securityProfile) {
351 if (config.trustForwardHeaders()) {
354 return trustedProxyPolicy(securityProfile).trustForwardedHeader();
357 private static boolean forwardedOnly(
final JettyServerConfig config,
final HttpSecurityProfile securityProfile) {
358 if (config.trustForwardHeaders()) {
359 return config.forwardedOnly();
361 return trustedProxyPolicy(securityProfile).forwardedOnly();
364 private static TrustedProxyPolicy trustedProxyPolicy(
final HttpSecurityProfile securityProfile) {
365 if (securityProfile ==
null || securityProfile.trustedProxies() ==
null) {
368 return securityProfile.trustedProxies();
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)