Skip to content

Commit

Permalink
Merge pull request #42448 from qingbozhang
Browse files Browse the repository at this point in the history
* pr/42448:
  Polish 'Add support for 'server.jetty.max-form-key' property'
  Add support for 'server.jetty.max-form-key' property

Closes gh-42448
  • Loading branch information
philwebb committed Oct 3, 2024
2 parents 90f375e + 58a1b2b commit f29d492
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,11 @@ public static class Jetty {
*/
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.
*/
Expand Down Expand Up @@ -1180,6 +1185,14 @@ public void setMaxHttpFormPostSize(DataSize maxHttpFormPostSize) {
this.maxHttpFormPostSize = maxHttpFormPostSize;
}

public int getMaxFormKeys() {
return this.maxFormKeys;
}

public void setMaxFormKeys(int maxFormKeys) {
this.maxFormKeys = maxFormKeys;
}

public Duration getConnectionIdleTimeout() {
return this.connectionIdleTimeout;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -19,21 +19,23 @@
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;
Expand Down Expand Up @@ -83,18 +85,21 @@ public void customize(ConfigurableJettyWebServerFactory factory) {
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));
Expand All @@ -112,43 +117,63 @@ private boolean getOrDeduceUseForwardHeaders() {
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,
Expand Down Expand Up @@ -176,61 +201,10 @@ private String getLogFormat(ServerProperties.Jetty.Accesslog properties) {
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);
}
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,15 @@ void jettyMaxHttpFormPostSizeMatchesDefault() {
.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())
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -26,6 +26,7 @@
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;
Expand Down Expand Up @@ -324,10 +325,23 @@ void customIdleTimeout() {
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)
Expand All @@ -344,9 +358,7 @@ private List<Integer> getResponseHeaderSizes(JettyWebServer server) {

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()
Expand All @@ -361,6 +373,11 @@ private List<Integer> getHeaderSizes(JettyWebServer server, Function<HttpConfigu
return requestHeaderSizes;
}

private void startAndStopToMakeInternalsAvailable(JettyWebServer server) {
server.start();
server.stop();
}

private BlockingQueue<?> getQueue(ThreadPool threadPool) {
return ReflectionTestUtils.invokeMethod(threadPool, "getQueue");
}
Expand Down

0 comments on commit f29d492

Please sign in to comment.