64public final class JettyServerFactory {
66 private static final TimingRecorder NOOP_TIMING_RECORDER = sample -> {
69 private JettyServerFactory() {
74 return create(
config, routeRegistry, jsonCodec,
null, List.of(), List.of(), HttpSecurityProfile.defaults(),
80 final List<JettyMiddleware> middlewares) {
81 return create(
config, routeRegistry, jsonCodec, tokenVerifier, authPolicies, middlewares,
87 final List<JettyMiddleware> middlewares,
final HttpSecurityProfile securityProfile,
90 Objects.requireNonNull(
config,
"config");
91 Objects.requireNonNull(routeRegistry,
"routeRegistry");
92 Objects.requireNonNull(jsonCodec,
"jsonCodec");
93 registerBuiltinRoutes(routeRegistry,
config, jsonCodec, tokenVerifier, securityProfile, requestIdGenerator,
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());
102 final var server =
new Server(pool);
103 server.setStopAtShutdown(
config.stopAtShutdown());
104 server.setStopTimeout(
config.stopTimeoutMs());
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());
111 connector.setPort(
config.port());
112 connector.setIdleTimeout(
config.idleTimeoutMs());
113 connector.setAcceptQueueSize(
config.acceptQueueSize());
114 connector.setReuseAddress(
config.reuseAddress());
115 server.addConnector(connector);
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);
134 requestIdGenerator, timingRecorder);
135 JettyBuiltinModule.registerRoutes(routeRegistry, context);
139 final TokenVerifier tokenVerifier,
final List<JettyModule> modules) {
140 return create(
config, jsonCodec, tokenVerifier, modules, HttpSecurityProfile.defaults(),
145 final TokenVerifier tokenVerifier,
final List<JettyModule> modules,
146 final HttpSecurityProfile securityProfile,
final RequestIdGenerator requestIdGenerator,
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);
159 JettyBuiltinModule.registerRoutes(routeRegistry, context);
161 return create(
config, routeRegistry, jsonCodec, tokenVerifier, authPolicyRegistry.policies(),
162 middlewareRegistry.middlewares(), securityProfile, requestIdGenerator, timingRecorder);
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());
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()) {
178 return new JettyAuthHandler(delegate, tokenVerifier, jsonCodec).authPolicies(policies);
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);
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);
198 return List.copyOf(merged);
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;
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)));
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()));
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);
240 if (trustForwardHeaders(config, securityProfile)) {
241 final var forwarded =
new ForwardedRequestCustomizer();
242 forwarded.setForwardedOnly(forwardedOnly(config, securityProfile));
243 httpConfig.addCustomizer(forwarded);
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) {
256 final var handler =
new SizeLimitHandler(requestLimit, responseLimit);
257 handler.setHandler(delegate);
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) {
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);
277 private static Handler applyThreadLimit(
final Handler delegate,
final JettyServerConfig config) {
278 if (config.maxRequestsPerRemoteIp() <= 0) {
282 final String forwardedHeader;
283 final boolean rfc7239;
284 if (config.trustForwardHeaders()) {
285 forwardedHeader = config.forwardedOnly() ?
"Forwarded" :
"X-Forwarded-For";
286 rfc7239 = config.forwardedOnly();
288 forwardedHeader =
null;
292 final var handler =
new ThreadLimitHandler(forwardedHeader, rfc7239);
293 handler.setThreadLimit(config.maxRequestsPerRemoteIp());
294 handler.setHandler(delegate);
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);
305 private static long normalizeLimit(
final long value) {
306 return value <= 0 ? -1L : value;
309 private static int maxConcurrentRequests(
final HttpSecurityProfile securityProfile) {
310 if (securityProfile ==
null || securityProfile.rateLimit() ==
null) {
313 return Math.max(0, securityProfile.rateLimit().maxConcurrentRequests());
317 final HttpSecurityProfile securityProfile) {
318 if (config.trustForwardHeaders()) {
321 return trustedProxyPolicy(securityProfile).trustForwardedHeader();
324 private static boolean forwardedOnly(
final JettyServerConfig config,
final HttpSecurityProfile securityProfile) {
325 if (config.trustForwardHeaders()) {
326 return config.forwardedOnly();
328 return trustedProxyPolicy(securityProfile).forwardedOnly();
331 private static TrustedProxyPolicy trustedProxyPolicy(
final HttpSecurityProfile securityProfile) {
332 if (securityProfile ==
null || securityProfile.trustedProxies() ==
null) {
335 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)