Browse Source

Merge pull request #42448 from qingbozhang

* pr/42448:
  Polish 'Add support for 'server.jetty.max-form-key' property'
  Add support for 'server.jetty.max-form-key' property

Closes gh-42448
pull/42503/head
Phillip Webb 1 year ago
parent
commit
f29d49211f
  1. 13
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java
  2. 154
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java
  3. 9
      spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java
  4. 31
      spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizerTests.java

13
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java

@ -1148,6 +1148,11 @@ public class ServerProperties { @@ -1148,6 +1148,11 @@ public class ServerProperties {
*/
private DataSize maxHttpFormPostSize = DataSize.ofBytes(200000);
/**
* Maximum number of form keys.
*/
private int maxFormKeys = 1000;
/**
* Time that the connection can be idle before it is closed.
*/
@ -1180,6 +1185,14 @@ public class ServerProperties { @@ -1180,6 +1185,14 @@ public class ServerProperties {
this.maxHttpFormPostSize = maxHttpFormPostSize;
}
public int getMaxFormKeys() {
return this.maxFormKeys;
}
public void setMaxFormKeys(int maxFormKeys) {
this.maxFormKeys = maxFormKeys;
}
public Duration getConnectionIdleTimeout() {
return this.connectionIdleTimeout;
}

154
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,21 +19,23 @@ package org.springframework.boot.autoconfigure.web.embedded; @@ -19,21 +19,23 @@ package org.springframework.boot.autoconfigure.web.embedded;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.CustomRequestLog;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.RequestLogWriter;
import org.eclipse.jetty.server.Server;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.embedded.jetty.ConfigurableJettyWebServerFactory;
import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;
@ -83,18 +85,21 @@ public class JettyWebServerFactoryCustomizer @@ -83,18 +85,21 @@ public class JettyWebServerFactoryCustomizer
map.from(this.serverProperties::getMaxHttpRequestHeaderSize)
.asInt(DataSize::toBytes)
.when(this::isPositive)
.to((maxHttpRequestHeaderSize) -> factory
.addServerCustomizers(new MaxHttpRequestHeaderSizeCustomizer(maxHttpRequestHeaderSize)));
.to(customizeHttpConfigurations(factory, HttpConfiguration::setRequestHeaderSize));
map.from(properties::getMaxHttpResponseHeaderSize)
.asInt(DataSize::toBytes)
.when(this::isPositive)
.to((maxHttpResponseHeaderSize) -> factory
.addServerCustomizers(new MaxHttpResponseHeaderSizeCustomizer(maxHttpResponseHeaderSize)));
.to(customizeHttpConfigurations(factory, HttpConfiguration::setResponseHeaderSize));
map.from(properties::getMaxHttpFormPostSize)
.asInt(DataSize::toBytes)
.when(this::isPositive)
.to((maxHttpFormPostSize) -> customizeMaxHttpFormPostSize(factory, maxHttpFormPostSize));
map.from(properties::getConnectionIdleTimeout).to((idleTimeout) -> customizeIdleTimeout(factory, idleTimeout));
.to(customizeServletContextHandler(factory, ServletContextHandler::setMaxFormContentSize));
map.from(properties::getMaxFormKeys)
.when(this::isPositive)
.to(customizeServletContextHandler(factory, ServletContextHandler::setMaxFormKeys));
map.from(properties::getConnectionIdleTimeout)
.as(Duration::toMillis)
.to(customizeAbstractConnectors(factory, AbstractConnector::setIdleTimeout));
map.from(properties::getAccesslog)
.when(ServerProperties.Jetty.Accesslog::isEnabled)
.to((accesslog) -> customizeAccessLog(factory, accesslog));
@ -112,43 +117,63 @@ public class JettyWebServerFactoryCustomizer @@ -112,43 +117,63 @@ public class JettyWebServerFactoryCustomizer
return this.serverProperties.getForwardHeadersStrategy().equals(ServerProperties.ForwardHeadersStrategy.NATIVE);
}
private void customizeIdleTimeout(ConfigurableJettyWebServerFactory factory, Duration connectionTimeout) {
factory.addServerCustomizers((server) -> {
for (org.eclipse.jetty.server.Connector connector : server.getConnectors()) {
if (connector instanceof AbstractConnector abstractConnector) {
abstractConnector.setIdleTimeout(connectionTimeout.toMillis());
}
}
private <T> Consumer<T> customizeHttpConfigurations(ConfigurableJettyWebServerFactory factory,
BiConsumer<HttpConfiguration, T> action) {
return customizeConnectionFactories(factory, HttpConfiguration.ConnectionFactory.class,
(connectionFactory, value) -> action.accept(connectionFactory.getHttpConfiguration(), value));
}
private <V, F> Consumer<V> customizeConnectionFactories(ConfigurableJettyWebServerFactory factory,
Class<F> connectionFactoryType, BiConsumer<F, V> action) {
return customizeConnectors(factory, Connector.class, (connector, value) -> {
Stream<ConnectionFactory> connectionFactories = connector.getConnectionFactories().stream();
forEach(connectionFactories, connectionFactoryType, action, value);
});
}
private void customizeMaxHttpFormPostSize(ConfigurableJettyWebServerFactory factory, int maxHttpFormPostSize) {
factory.addServerCustomizers(new JettyServerCustomizer() {
private <V> Consumer<V> customizeAbstractConnectors(ConfigurableJettyWebServerFactory factory,
BiConsumer<AbstractConnector, V> action) {
return customizeConnectors(factory, AbstractConnector.class, action);
}
@Override
public void customize(Server server) {
setHandlerMaxHttpFormPostSize(server.getHandlers());
}
private <V, C> Consumer<V> customizeConnectors(ConfigurableJettyWebServerFactory factory, Class<C> connectorType,
BiConsumer<C, V> action) {
return (value) -> factory.addServerCustomizers((server) -> {
Stream<Connector> connectors = Arrays.stream(server.getConnectors());
forEach(connectors, connectorType, action, value);
});
}
private void setHandlerMaxHttpFormPostSize(List<Handler> handlers) {
for (Handler handler : handlers) {
setHandlerMaxHttpFormPostSize(handler);
}
}
private <V> Consumer<V> customizeServletContextHandler(ConfigurableJettyWebServerFactory factory,
BiConsumer<ServletContextHandler, V> action) {
return customizeHandlers(factory, ServletContextHandler.class, action);
}
private void setHandlerMaxHttpFormPostSize(Handler handler) {
if (handler instanceof ServletContextHandler contextHandler) {
contextHandler.setMaxFormContentSize(maxHttpFormPostSize);
}
else if (handler instanceof Handler.Wrapper wrapper) {
setHandlerMaxHttpFormPostSize(wrapper.getHandler());
}
else if (handler instanceof Handler.Collection collection) {
setHandlerMaxHttpFormPostSize(collection.getHandlers());
}
private <V, H> Consumer<V> customizeHandlers(ConfigurableJettyWebServerFactory factory, Class<H> handlerType,
BiConsumer<H, V> action) {
return (value) -> factory.addServerCustomizers((server) -> {
List<Handler> handlers = server.getHandlers();
forEachHandler(handlers, handlerType, action, value);
});
}
@SuppressWarnings("unchecked")
private <V, H> void forEachHandler(List<Handler> handlers, Class<H> handlerType, BiConsumer<H, V> action, V value) {
for (Handler handler : handlers) {
if (handlerType.isInstance(handler)) {
action.accept((H) handler, value);
}
if (handler instanceof Handler.Wrapper wrapper) {
forEachHandler(wrapper.getHandlers(), handlerType, action, value);
}
if (handler instanceof Handler.Collection collection) {
forEachHandler(collection.getHandlers(), handlerType, action, value);
}
}
}
});
private <T, V> void forEach(Stream<?> elements, Class<T> type, BiConsumer<T, V> action, V value) {
elements.filter(type::isInstance).map(type::cast).forEach((element) -> action.accept(element, value));
}
private void customizeAccessLog(ConfigurableJettyWebServerFactory factory,
@ -176,61 +201,10 @@ public class JettyWebServerFactoryCustomizer @@ -176,61 +201,10 @@ public class JettyWebServerFactoryCustomizer
if (properties.getCustomFormat() != null) {
return properties.getCustomFormat();
}
else if (ServerProperties.Jetty.Accesslog.FORMAT.EXTENDED_NCSA.equals(properties.getFormat())) {
if (ServerProperties.Jetty.Accesslog.FORMAT.EXTENDED_NCSA.equals(properties.getFormat())) {
return CustomRequestLog.EXTENDED_NCSA_FORMAT;
}
return CustomRequestLog.NCSA_FORMAT;
}
private static class MaxHttpRequestHeaderSizeCustomizer implements JettyServerCustomizer {
private final int maxRequestHeaderSize;
MaxHttpRequestHeaderSizeCustomizer(int maxRequestHeaderSize) {
this.maxRequestHeaderSize = maxRequestHeaderSize;
}
@Override
public void customize(Server server) {
Arrays.stream(server.getConnectors()).forEach(this::customize);
}
private void customize(org.eclipse.jetty.server.Connector connector) {
connector.getConnectionFactories().forEach(this::customize);
}
private void customize(ConnectionFactory factory) {
if (factory instanceof HttpConfiguration.ConnectionFactory) {
((HttpConfiguration.ConnectionFactory) factory).getHttpConfiguration()
.setRequestHeaderSize(this.maxRequestHeaderSize);
}
}
}
private static class MaxHttpResponseHeaderSizeCustomizer implements JettyServerCustomizer {
private final int maxResponseHeaderSize;
MaxHttpResponseHeaderSizeCustomizer(int maxResponseHeaderSize) {
this.maxResponseHeaderSize = maxResponseHeaderSize;
}
@Override
public void customize(Server server) {
Arrays.stream(server.getConnectors()).forEach(this::customize);
}
private void customize(org.eclipse.jetty.server.Connector connector) {
connector.getConnectionFactories().forEach(this::customize);
}
private void customize(ConnectionFactory factory) {
if (factory instanceof HttpConfiguration.ConnectionFactory httpConnectionFactory) {
httpConnectionFactory.getHttpConfiguration().setResponseHeaderSize(this.maxResponseHeaderSize);
}
}
}
}

9
spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java

@ -465,6 +465,15 @@ class ServerPropertiesTests { @@ -465,6 +465,15 @@ class ServerPropertiesTests {
.isEqualTo(((ServletContextHandler) server.getHandler()).getMaxFormContentSize());
}
@Test
void jettyMaxFormKeysMatchesDefault() {
JettyServletWebServerFactory jettyFactory = new JettyServletWebServerFactory(0);
JettyWebServer jetty = (JettyWebServer) jettyFactory.getWebServer();
Server server = jetty.getServer();
assertThat(this.properties.getJetty().getMaxFormKeys())
.isEqualTo(((ServletContextHandler) server.getHandler()).getMaxFormKeys());
}
@Test
void undertowMaxHttpPostSizeMatchesDefault() {
assertThat(this.properties.getUndertow().getMaxHttpPostSize().toBytes())

31
spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizerTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -26,6 +26,7 @@ import java.util.concurrent.BlockingQueue; @@ -26,6 +26,7 @@ import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.function.Function;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.CustomRequestLog;
@ -324,10 +325,23 @@ class JettyWebServerFactoryCustomizerTests { @@ -324,10 +325,23 @@ class JettyWebServerFactoryCustomizerTests {
assertThat(timeouts).containsOnly(60000L);
}
@Test
void customMaxFormKeys() {
bind("server.jetty.max-form-keys=2048");
JettyWebServer server = customizeAndGetServer();
startAndStopToMakeInternalsAvailable(server);
List<Integer> maxFormKeys = server.getServer()
.getHandlers()
.stream()
.filter(ServletContextHandler.class::isInstance)
.map(ServletContextHandler.class::cast)
.map(ServletContextHandler::getMaxFormKeys)
.toList();
assertThat(maxFormKeys).containsOnly(2048);
}
private List<Long> connectorsIdleTimeouts(JettyWebServer server) {
// Start (and directly stop) server to have connectors available
server.start();
server.stop();
startAndStopToMakeInternalsAvailable(server);
return Arrays.stream(server.getServer().getConnectors())
.filter((connector) -> connector instanceof AbstractConnector)
.map(Connector::getIdleTimeout)
@ -344,9 +358,7 @@ class JettyWebServerFactoryCustomizerTests { @@ -344,9 +358,7 @@ class JettyWebServerFactoryCustomizerTests {
private List<Integer> getHeaderSizes(JettyWebServer server, Function<HttpConfiguration, Integer> provider) {
List<Integer> requestHeaderSizes = new ArrayList<>();
// Start (and directly stop) server to have connectors available
server.start();
server.stop();
startAndStopToMakeInternalsAvailable(server);
Connector[] connectors = server.getServer().getConnectors();
for (Connector connector : connectors) {
connector.getConnectionFactories()
@ -361,6 +373,11 @@ class JettyWebServerFactoryCustomizerTests { @@ -361,6 +373,11 @@ class JettyWebServerFactoryCustomizerTests {
return requestHeaderSizes;
}
private void startAndStopToMakeInternalsAvailable(JettyWebServer server) {
server.start();
server.stop();
}
private BlockingQueue<?> getQueue(ThreadPool threadPool) {
return ReflectionTestUtils.invokeMethod(threadPool, "getQueue");
}

Loading…
Cancel
Save