Skip to content

Commit f29d492

Browse files
committed
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
2 parents 90f375e + 58a1b2b commit f29d492

File tree

4 files changed

+110
-97
lines changed

4 files changed

+110
-97
lines changed

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,6 +1148,11 @@ public static class Jetty {
11481148
*/
11491149
private DataSize maxHttpFormPostSize = DataSize.ofBytes(200000);
11501150

1151+
/**
1152+
* Maximum number of form keys.
1153+
*/
1154+
private int maxFormKeys = 1000;
1155+
11511156
/**
11521157
* Time that the connection can be idle before it is closed.
11531158
*/
@@ -1180,6 +1185,14 @@ public void setMaxHttpFormPostSize(DataSize maxHttpFormPostSize) {
11801185
this.maxHttpFormPostSize = maxHttpFormPostSize;
11811186
}
11821187

1188+
public int getMaxFormKeys() {
1189+
return this.maxFormKeys;
1190+
}
1191+
1192+
public void setMaxFormKeys(int maxFormKeys) {
1193+
this.maxFormKeys = maxFormKeys;
1194+
}
1195+
11831196
public Duration getConnectionIdleTimeout() {
11841197
return this.connectionIdleTimeout;
11851198
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,21 +19,23 @@
1919
import java.time.Duration;
2020
import java.util.Arrays;
2121
import java.util.List;
22+
import java.util.function.BiConsumer;
23+
import java.util.function.Consumer;
24+
import java.util.stream.Stream;
2225

2326
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
2427
import org.eclipse.jetty.server.AbstractConnector;
2528
import org.eclipse.jetty.server.ConnectionFactory;
29+
import org.eclipse.jetty.server.Connector;
2630
import org.eclipse.jetty.server.CustomRequestLog;
2731
import org.eclipse.jetty.server.Handler;
2832
import org.eclipse.jetty.server.HttpConfiguration;
2933
import org.eclipse.jetty.server.RequestLogWriter;
30-
import org.eclipse.jetty.server.Server;
3134

3235
import org.springframework.boot.autoconfigure.web.ServerProperties;
3336
import org.springframework.boot.cloud.CloudPlatform;
3437
import org.springframework.boot.context.properties.PropertyMapper;
3538
import org.springframework.boot.web.embedded.jetty.ConfigurableJettyWebServerFactory;
36-
import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer;
3739
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
3840
import org.springframework.core.Ordered;
3941
import org.springframework.core.env.Environment;
@@ -83,18 +85,21 @@ public void customize(ConfigurableJettyWebServerFactory factory) {
8385
map.from(this.serverProperties::getMaxHttpRequestHeaderSize)
8486
.asInt(DataSize::toBytes)
8587
.when(this::isPositive)
86-
.to((maxHttpRequestHeaderSize) -> factory
87-
.addServerCustomizers(new MaxHttpRequestHeaderSizeCustomizer(maxHttpRequestHeaderSize)));
88+
.to(customizeHttpConfigurations(factory, HttpConfiguration::setRequestHeaderSize));
8889
map.from(properties::getMaxHttpResponseHeaderSize)
8990
.asInt(DataSize::toBytes)
9091
.when(this::isPositive)
91-
.to((maxHttpResponseHeaderSize) -> factory
92-
.addServerCustomizers(new MaxHttpResponseHeaderSizeCustomizer(maxHttpResponseHeaderSize)));
92+
.to(customizeHttpConfigurations(factory, HttpConfiguration::setResponseHeaderSize));
9393
map.from(properties::getMaxHttpFormPostSize)
9494
.asInt(DataSize::toBytes)
9595
.when(this::isPositive)
96-
.to((maxHttpFormPostSize) -> customizeMaxHttpFormPostSize(factory, maxHttpFormPostSize));
97-
map.from(properties::getConnectionIdleTimeout).to((idleTimeout) -> customizeIdleTimeout(factory, idleTimeout));
96+
.to(customizeServletContextHandler(factory, ServletContextHandler::setMaxFormContentSize));
97+
map.from(properties::getMaxFormKeys)
98+
.when(this::isPositive)
99+
.to(customizeServletContextHandler(factory, ServletContextHandler::setMaxFormKeys));
100+
map.from(properties::getConnectionIdleTimeout)
101+
.as(Duration::toMillis)
102+
.to(customizeAbstractConnectors(factory, AbstractConnector::setIdleTimeout));
98103
map.from(properties::getAccesslog)
99104
.when(ServerProperties.Jetty.Accesslog::isEnabled)
100105
.to((accesslog) -> customizeAccessLog(factory, accesslog));
@@ -112,43 +117,63 @@ private boolean getOrDeduceUseForwardHeaders() {
112117
return this.serverProperties.getForwardHeadersStrategy().equals(ServerProperties.ForwardHeadersStrategy.NATIVE);
113118
}
114119

115-
private void customizeIdleTimeout(ConfigurableJettyWebServerFactory factory, Duration connectionTimeout) {
116-
factory.addServerCustomizers((server) -> {
117-
for (org.eclipse.jetty.server.Connector connector : server.getConnectors()) {
118-
if (connector instanceof AbstractConnector abstractConnector) {
119-
abstractConnector.setIdleTimeout(connectionTimeout.toMillis());
120-
}
121-
}
120+
private <T> Consumer<T> customizeHttpConfigurations(ConfigurableJettyWebServerFactory factory,
121+
BiConsumer<HttpConfiguration, T> action) {
122+
return customizeConnectionFactories(factory, HttpConfiguration.ConnectionFactory.class,
123+
(connectionFactory, value) -> action.accept(connectionFactory.getHttpConfiguration(), value));
124+
}
125+
126+
private <V, F> Consumer<V> customizeConnectionFactories(ConfigurableJettyWebServerFactory factory,
127+
Class<F> connectionFactoryType, BiConsumer<F, V> action) {
128+
return customizeConnectors(factory, Connector.class, (connector, value) -> {
129+
Stream<ConnectionFactory> connectionFactories = connector.getConnectionFactories().stream();
130+
forEach(connectionFactories, connectionFactoryType, action, value);
122131
});
123132
}
124133

125-
private void customizeMaxHttpFormPostSize(ConfigurableJettyWebServerFactory factory, int maxHttpFormPostSize) {
126-
factory.addServerCustomizers(new JettyServerCustomizer() {
134+
private <V> Consumer<V> customizeAbstractConnectors(ConfigurableJettyWebServerFactory factory,
135+
BiConsumer<AbstractConnector, V> action) {
136+
return customizeConnectors(factory, AbstractConnector.class, action);
137+
}
127138

128-
@Override
129-
public void customize(Server server) {
130-
setHandlerMaxHttpFormPostSize(server.getHandlers());
131-
}
139+
private <V, C> Consumer<V> customizeConnectors(ConfigurableJettyWebServerFactory factory, Class<C> connectorType,
140+
BiConsumer<C, V> action) {
141+
return (value) -> factory.addServerCustomizers((server) -> {
142+
Stream<Connector> connectors = Arrays.stream(server.getConnectors());
143+
forEach(connectors, connectorType, action, value);
144+
});
145+
}
132146

133-
private void setHandlerMaxHttpFormPostSize(List<Handler> handlers) {
134-
for (Handler handler : handlers) {
135-
setHandlerMaxHttpFormPostSize(handler);
136-
}
137-
}
147+
private <V> Consumer<V> customizeServletContextHandler(ConfigurableJettyWebServerFactory factory,
148+
BiConsumer<ServletContextHandler, V> action) {
149+
return customizeHandlers(factory, ServletContextHandler.class, action);
150+
}
138151

139-
private void setHandlerMaxHttpFormPostSize(Handler handler) {
140-
if (handler instanceof ServletContextHandler contextHandler) {
141-
contextHandler.setMaxFormContentSize(maxHttpFormPostSize);
142-
}
143-
else if (handler instanceof Handler.Wrapper wrapper) {
144-
setHandlerMaxHttpFormPostSize(wrapper.getHandler());
145-
}
146-
else if (handler instanceof Handler.Collection collection) {
147-
setHandlerMaxHttpFormPostSize(collection.getHandlers());
148-
}
152+
private <V, H> Consumer<V> customizeHandlers(ConfigurableJettyWebServerFactory factory, Class<H> handlerType,
153+
BiConsumer<H, V> action) {
154+
return (value) -> factory.addServerCustomizers((server) -> {
155+
List<Handler> handlers = server.getHandlers();
156+
forEachHandler(handlers, handlerType, action, value);
157+
});
158+
}
159+
160+
@SuppressWarnings("unchecked")
161+
private <V, H> void forEachHandler(List<Handler> handlers, Class<H> handlerType, BiConsumer<H, V> action, V value) {
162+
for (Handler handler : handlers) {
163+
if (handlerType.isInstance(handler)) {
164+
action.accept((H) handler, value);
165+
}
166+
if (handler instanceof Handler.Wrapper wrapper) {
167+
forEachHandler(wrapper.getHandlers(), handlerType, action, value);
149168
}
169+
if (handler instanceof Handler.Collection collection) {
170+
forEachHandler(collection.getHandlers(), handlerType, action, value);
171+
}
172+
}
173+
}
150174

151-
});
175+
private <T, V> void forEach(Stream<?> elements, Class<T> type, BiConsumer<T, V> action, V value) {
176+
elements.filter(type::isInstance).map(type::cast).forEach((element) -> action.accept(element, value));
152177
}
153178

154179
private void customizeAccessLog(ConfigurableJettyWebServerFactory factory,
@@ -176,61 +201,10 @@ private String getLogFormat(ServerProperties.Jetty.Accesslog properties) {
176201
if (properties.getCustomFormat() != null) {
177202
return properties.getCustomFormat();
178203
}
179-
else if (ServerProperties.Jetty.Accesslog.FORMAT.EXTENDED_NCSA.equals(properties.getFormat())) {
204+
if (ServerProperties.Jetty.Accesslog.FORMAT.EXTENDED_NCSA.equals(properties.getFormat())) {
180205
return CustomRequestLog.EXTENDED_NCSA_FORMAT;
181206
}
182207
return CustomRequestLog.NCSA_FORMAT;
183208
}
184209

185-
private static class MaxHttpRequestHeaderSizeCustomizer implements JettyServerCustomizer {
186-
187-
private final int maxRequestHeaderSize;
188-
189-
MaxHttpRequestHeaderSizeCustomizer(int maxRequestHeaderSize) {
190-
this.maxRequestHeaderSize = maxRequestHeaderSize;
191-
}
192-
193-
@Override
194-
public void customize(Server server) {
195-
Arrays.stream(server.getConnectors()).forEach(this::customize);
196-
}
197-
198-
private void customize(org.eclipse.jetty.server.Connector connector) {
199-
connector.getConnectionFactories().forEach(this::customize);
200-
}
201-
202-
private void customize(ConnectionFactory factory) {
203-
if (factory instanceof HttpConfiguration.ConnectionFactory) {
204-
((HttpConfiguration.ConnectionFactory) factory).getHttpConfiguration()
205-
.setRequestHeaderSize(this.maxRequestHeaderSize);
206-
}
207-
}
208-
209-
}
210-
211-
private static class MaxHttpResponseHeaderSizeCustomizer implements JettyServerCustomizer {
212-
213-
private final int maxResponseHeaderSize;
214-
215-
MaxHttpResponseHeaderSizeCustomizer(int maxResponseHeaderSize) {
216-
this.maxResponseHeaderSize = maxResponseHeaderSize;
217-
}
218-
219-
@Override
220-
public void customize(Server server) {
221-
Arrays.stream(server.getConnectors()).forEach(this::customize);
222-
}
223-
224-
private void customize(org.eclipse.jetty.server.Connector connector) {
225-
connector.getConnectionFactories().forEach(this::customize);
226-
}
227-
228-
private void customize(ConnectionFactory factory) {
229-
if (factory instanceof HttpConfiguration.ConnectionFactory httpConnectionFactory) {
230-
httpConnectionFactory.getHttpConfiguration().setResponseHeaderSize(this.maxResponseHeaderSize);
231-
}
232-
}
233-
234-
}
235-
236210
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,15 @@ void jettyMaxHttpFormPostSizeMatchesDefault() {
465465
.isEqualTo(((ServletContextHandler) server.getHandler()).getMaxFormContentSize());
466466
}
467467

468+
@Test
469+
void jettyMaxFormKeysMatchesDefault() {
470+
JettyServletWebServerFactory jettyFactory = new JettyServletWebServerFactory(0);
471+
JettyWebServer jetty = (JettyWebServer) jettyFactory.getWebServer();
472+
Server server = jetty.getServer();
473+
assertThat(this.properties.getJetty().getMaxFormKeys())
474+
.isEqualTo(((ServletContextHandler) server.getHandler()).getMaxFormKeys());
475+
}
476+
468477
@Test
469478
void undertowMaxHttpPostSizeMatchesDefault() {
470479
assertThat(this.properties.getUndertow().getMaxHttpPostSize().toBytes())

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

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626
import java.util.concurrent.SynchronousQueue;
2727
import java.util.function.Function;
2828

29+
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
2930
import org.eclipse.jetty.server.AbstractConnector;
3031
import org.eclipse.jetty.server.Connector;
3132
import org.eclipse.jetty.server.CustomRequestLog;
@@ -324,10 +325,23 @@ void customIdleTimeout() {
324325
assertThat(timeouts).containsOnly(60000L);
325326
}
326327

328+
@Test
329+
void customMaxFormKeys() {
330+
bind("server.jetty.max-form-keys=2048");
331+
JettyWebServer server = customizeAndGetServer();
332+
startAndStopToMakeInternalsAvailable(server);
333+
List<Integer> maxFormKeys = server.getServer()
334+
.getHandlers()
335+
.stream()
336+
.filter(ServletContextHandler.class::isInstance)
337+
.map(ServletContextHandler.class::cast)
338+
.map(ServletContextHandler::getMaxFormKeys)
339+
.toList();
340+
assertThat(maxFormKeys).containsOnly(2048);
341+
}
342+
327343
private List<Long> connectorsIdleTimeouts(JettyWebServer server) {
328-
// Start (and directly stop) server to have connectors available
329-
server.start();
330-
server.stop();
344+
startAndStopToMakeInternalsAvailable(server);
331345
return Arrays.stream(server.getServer().getConnectors())
332346
.filter((connector) -> connector instanceof AbstractConnector)
333347
.map(Connector::getIdleTimeout)
@@ -344,9 +358,7 @@ private List<Integer> getResponseHeaderSizes(JettyWebServer server) {
344358

345359
private List<Integer> getHeaderSizes(JettyWebServer server, Function<HttpConfiguration, Integer> provider) {
346360
List<Integer> requestHeaderSizes = new ArrayList<>();
347-
// Start (and directly stop) server to have connectors available
348-
server.start();
349-
server.stop();
361+
startAndStopToMakeInternalsAvailable(server);
350362
Connector[] connectors = server.getServer().getConnectors();
351363
for (Connector connector : connectors) {
352364
connector.getConnectionFactories()
@@ -361,6 +373,11 @@ private List<Integer> getHeaderSizes(JettyWebServer server, Function<HttpConfigu
361373
return requestHeaderSizes;
362374
}
363375

376+
private void startAndStopToMakeInternalsAvailable(JettyWebServer server) {
377+
server.start();
378+
server.stop();
379+
}
380+
364381
private BlockingQueue<?> getQueue(ThreadPool threadPool) {
365382
return ReflectionTestUtils.invokeMethod(threadPool, "getQueue");
366383
}

0 commit comments

Comments
 (0)