From 3e4a9f5204ee2fe989583b17b77d8dab8558282a Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Tue, 6 Jun 2023 09:39:33 +0200 Subject: [PATCH] Add property to limit maximum connections for Jetty Closes gh-35899 --- .../boot/autoconfigure/web/ServerProperties.java | 14 ++++++++++++++ .../embedded/JettyWebServerFactoryCustomizer.java | 1 + .../jetty/ConfigurableJettyWebServerFactory.java | 8 ++++++++ .../jetty/JettyReactiveWebServerFactory.java | 12 ++++++++++++ .../jetty/JettyServletWebServerFactory.java | 12 ++++++++++++ .../jetty/JettyReactiveWebServerFactoryTests.java | 13 +++++++++++++ .../jetty/JettyServletWebServerFactoryTests.java | 13 +++++++++++++ 7 files changed, 73 insertions(+) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java index a4e5bcf2d1a..681bd7d8384 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java @@ -1123,6 +1123,12 @@ public class ServerProperties { */ private DataSize maxHttpResponseHeaderSize = DataSize.ofKilobytes(8); + /** + * Maximum number of connections that the server accepts and processes at any + * given time. + */ + private int maxConnections = -1; + public Accesslog getAccesslog() { return this.accesslog; } @@ -1155,6 +1161,14 @@ public class ServerProperties { this.maxHttpResponseHeaderSize = maxHttpResponseHeaderSize; } + public int getMaxConnections() { + return this.maxConnections; + } + + public void setMaxConnections(int maxConnections) { + this.maxConnections = maxConnections; + } + /** * Jetty access log properties. */ diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java index 2248f1a7203..2c9d015cae9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java @@ -81,6 +81,7 @@ public class JettyWebServerFactoryCustomizer ServerProperties.Jetty.Threads threadProperties = properties.getThreads(); factory.setThreadPool(determineThreadPool(properties.getThreads())); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties::getMaxConnections).to(factory::setMaxConnections); map.from(threadProperties::getAcceptors).to(factory::setAcceptors); map.from(threadProperties::getSelectors).to(factory::setSelectors); map.from(this.serverProperties::getMaxHttpRequestHeaderSize) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/ConfigurableJettyWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/ConfigurableJettyWebServerFactory.java index 5f8a927b403..65cae98947d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/ConfigurableJettyWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/ConfigurableJettyWebServerFactory.java @@ -25,6 +25,7 @@ import org.springframework.boot.web.server.ConfigurableWebServerFactory; * {@link ConfigurableWebServerFactory} for Jetty-specific features. * * @author Brian Clozel + * @author Moritz Halbritter * @since 2.0.0 * @see JettyServletWebServerFactory * @see JettyReactiveWebServerFactory @@ -63,4 +64,11 @@ public interface ConfigurableJettyWebServerFactory extends ConfigurableWebServer */ void addServerCustomizers(JettyServerCustomizer... customizers); + /** + * Sets the maximum number of concurrent connections. + * @param maxConnections the maximum number of concurrent connections + * @since 3.2.0 + */ + void setMaxConnections(int maxConnections); + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java index 3e8049f4d73..3102dd4aa3d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java @@ -29,6 +29,7 @@ import org.apache.commons.logging.LogFactory; import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.ConnectionFactory; +import org.eclipse.jetty.server.ConnectionLimit; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; @@ -55,6 +56,7 @@ import org.springframework.util.StringUtils; * {@link ReactiveWebServerFactory} that can be used to create {@link JettyWebServer}s. * * @author Brian Clozel + * @author Moritz Halbritter * @since 2.0.0 */ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFactory @@ -80,6 +82,8 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact private ThreadPool threadPool; + private int maxConnections = -1; + /** * Create a new {@link JettyServletWebServerFactory} instance. */ @@ -118,6 +122,11 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact this.jettyServerCustomizers.addAll(Arrays.asList(customizers)); } + @Override + public void setMaxConnections(int maxConnections) { + this.maxConnections = maxConnections; + } + /** * Sets {@link JettyServerCustomizer}s that will be applied to the {@link Server} * before it is started. Calling this method will replace any existing customizers. @@ -180,6 +189,9 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact contextHandler.addServlet(servletHolder, "/"); server.setHandler(addHandlerWrappers(contextHandler)); JettyReactiveWebServerFactory.logger.info("Server initialized with port: " + port); + if (this.maxConnections > -1) { + server.addBean(new ConnectionLimit(this.maxConnections, server)); + } if (Ssl.isEnabled(getSsl())) { customizeSsl(server, address); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java index e060a372f07..4d93f13367f 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java @@ -43,6 +43,7 @@ import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.ConnectionFactory; +import org.eclipse.jetty.server.ConnectionLimit; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; @@ -101,6 +102,7 @@ import org.springframework.util.StringUtils; * @author EddĂș MelĂ©ndez * @author Venil Noronha * @author Henri Kerola + * @author Moritz Halbritter * @since 2.0.0 * @see #setPort(int) * @see #setConfigurations(Collection) @@ -129,6 +131,8 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor private ThreadPool threadPool; + private int maxConnections = -1; + /** * Create a new {@link JettyServletWebServerFactory} instance. */ @@ -163,6 +167,9 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor configureWebAppContext(context, initializers); server.setHandler(addHandlerWrappers(context)); this.logger.info("Server initialized with port: " + port); + if (this.maxConnections > -1) { + server.addBean(new ConnectionLimit(this.maxConnections, server)); + } if (Ssl.isEnabled(getSsl())) { customizeSsl(server, address); } @@ -458,6 +465,11 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor this.selectors = selectors; } + @Override + public void setMaxConnections(int maxConnections) { + this.maxConnections = maxConnections; + } + /** * Sets {@link JettyServerCustomizer}s that will be applied to the {@link Server} * before it is started. Calling this method will replace any existing customizers. diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactoryTests.java index 09ecc2c3cef..cf42ba29d7a 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactoryTests.java @@ -22,6 +22,7 @@ import java.time.Duration; import java.util.Arrays; import org.awaitility.Awaitility; +import org.eclipse.jetty.server.ConnectionLimit; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; @@ -47,6 +48,7 @@ import static org.mockito.Mockito.mock; * * @author Brian Clozel * @author Madhura Bhave + * @author Moritz Halbritter */ @Servlet5ClassPathOverrides class JettyReactiveWebServerFactoryTests extends AbstractReactiveWebServerFactoryTests { @@ -148,4 +150,15 @@ class JettyReactiveWebServerFactoryTests extends AbstractReactiveWebServerFactor this.webServer.stop(); } + @Test + void shouldApplyMaxConnections() { + JettyReactiveWebServerFactory factory = getFactory(); + factory.setMaxConnections(1); + this.webServer = factory.getWebServer(new EchoHandler()); + Server server = ((JettyWebServer) this.webServer).getServer(); + ConnectionLimit connectionLimit = server.getBean(ConnectionLimit.class); + assertThat(connectionLimit).isNotNull(); + assertThat(connectionLimit.getMaxConnections()).isOne(); + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactoryTests.java index 63fe2402231..f037202ca4e 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactoryTests.java @@ -40,6 +40,7 @@ import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpResponse; import org.apache.jasper.servlet.JspServlet; import org.awaitility.Awaitility; +import org.eclipse.jetty.server.ConnectionLimit; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; @@ -86,6 +87,7 @@ import static org.mockito.Mockito.mock; * @author Dave Syer * @author Andy Wilkinson * @author Henri Kerola + * @author Moritz Halbritter */ @Servlet5ClassPathOverrides class JettyServletWebServerFactoryTests extends AbstractServletWebServerFactoryTests { @@ -518,6 +520,17 @@ class JettyServletWebServerFactoryTests extends AbstractServletWebServerFactoryT assertThat(context.getErrorHandler()).isInstanceOf(CustomErrorHandler.class); } + @Test + void shouldApplyMaxConnections() { + JettyServletWebServerFactory factory = getFactory(); + factory.setMaxConnections(1); + this.webServer = factory.getWebServer(); + Server server = ((JettyWebServer) this.webServer).getServer(); + ConnectionLimit connectionLimit = server.getBean(ConnectionLimit.class); + assertThat(connectionLimit).isNotNull(); + assertThat(connectionLimit.getMaxConnections()).isOne(); + } + private WebAppContext findWebAppContext(JettyWebServer webServer) { return findWebAppContext(webServer.getServer().getHandler()); }