mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-07-05 00:56:58 +08:00
Add max http response header size configuration for tomcat and jetty
See gh-33553
This commit is contained in:
parent
ace31cd5b2
commit
93d46d11e9
@ -69,6 +69,8 @@ import org.springframework.util.unit.DataSize;
|
||||
* @author Victor Mandujano
|
||||
* @author Chris Bono
|
||||
* @author Parviz Rozikov
|
||||
* @author Florian Storz
|
||||
* @author Michael Weidmann
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
|
||||
@ -490,6 +492,11 @@ public class ServerProperties {
|
||||
*/
|
||||
private final Remoteip remoteip = new Remoteip();
|
||||
|
||||
/**
|
||||
* Maximum size of the HTTP response header.
|
||||
*/
|
||||
private DataSize maxHttpResponseHeaderSize = DataSize.ofKilobytes(8);
|
||||
|
||||
public DataSize getMaxHttpFormPostSize() {
|
||||
return this.maxHttpFormPostSize;
|
||||
}
|
||||
@ -646,6 +653,14 @@ public class ServerProperties {
|
||||
return this.remoteip;
|
||||
}
|
||||
|
||||
public DataSize getMaxHttpResponseHeaderSize() {
|
||||
return maxHttpResponseHeaderSize;
|
||||
}
|
||||
|
||||
public void setMaxHttpResponseHeaderSize(DataSize maxHttpResponseHeaderSize) {
|
||||
this.maxHttpResponseHeaderSize = maxHttpResponseHeaderSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tomcat access log properties.
|
||||
*/
|
||||
@ -1096,6 +1111,11 @@ public class ServerProperties {
|
||||
*/
|
||||
private Duration connectionIdleTimeout;
|
||||
|
||||
/**
|
||||
* Maximum size of the HTTP response header.
|
||||
*/
|
||||
private DataSize maxHttpResponseHeaderSize = DataSize.ofKilobytes(8);
|
||||
|
||||
public Accesslog getAccesslog() {
|
||||
return this.accesslog;
|
||||
}
|
||||
@ -1120,6 +1140,14 @@ public class ServerProperties {
|
||||
this.connectionIdleTimeout = connectionIdleTimeout;
|
||||
}
|
||||
|
||||
public DataSize getMaxHttpResponseHeaderSize() {
|
||||
return maxHttpResponseHeaderSize;
|
||||
}
|
||||
|
||||
public void setMaxHttpResponseHeaderSize(DataSize maxHttpResponseHeaderSize) {
|
||||
this.maxHttpResponseHeaderSize = maxHttpResponseHeaderSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Jetty access log properties.
|
||||
*/
|
||||
|
@ -53,6 +53,8 @@ import org.springframework.util.unit.DataSize;
|
||||
* @author Phillip Webb
|
||||
* @author HaiTao Zhang
|
||||
* @author Rafiullah Hamedy
|
||||
* @author Florian Storz
|
||||
* @author Michael Weidmann
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class JettyWebServerFactoryCustomizer
|
||||
@ -85,6 +87,9 @@ public class JettyWebServerFactoryCustomizer
|
||||
propertyMapper.from(properties::getMaxHttpRequestHeaderSize).whenNonNull().asInt(DataSize::toBytes)
|
||||
.when(this::isPositive).to((maxHttpRequestHeaderSize) -> factory
|
||||
.addServerCustomizers(new MaxHttpRequestHeaderSizeCustomizer(maxHttpRequestHeaderSize)));
|
||||
propertyMapper.from(jettyProperties::getMaxHttpResponseHeaderSize).whenNonNull().asInt(DataSize::toBytes)
|
||||
.when(this::isPositive).to((maxHttpResponseHeaderSize) -> factory
|
||||
.addServerCustomizers(new MaxHttpResponseHeaderSizeCustomizer(maxHttpResponseHeaderSize)));
|
||||
propertyMapper.from(jettyProperties::getMaxHttpFormPostSize).asInt(DataSize::toBytes).when(this::isPositive)
|
||||
.to((maxHttpFormPostSize) -> customizeMaxHttpFormPostSize(factory, maxHttpFormPostSize));
|
||||
propertyMapper.from(jettyProperties::getConnectionIdleTimeout).whenNonNull()
|
||||
@ -192,13 +197,7 @@ public class JettyWebServerFactoryCustomizer
|
||||
return CustomRequestLog.NCSA_FORMAT;
|
||||
}
|
||||
|
||||
private static class MaxHttpRequestHeaderSizeCustomizer implements JettyServerCustomizer {
|
||||
|
||||
private final int maxRequestHeaderSize;
|
||||
|
||||
MaxHttpRequestHeaderSizeCustomizer(int maxRequestHeaderSize) {
|
||||
this.maxRequestHeaderSize = maxRequestHeaderSize;
|
||||
}
|
||||
private record MaxHttpRequestHeaderSizeCustomizer(int maxRequestHeaderSize) implements JettyServerCustomizer {
|
||||
|
||||
@Override
|
||||
public void customize(Server server) {
|
||||
@ -215,7 +214,25 @@ public class JettyWebServerFactoryCustomizer
|
||||
.setRequestHeaderSize(this.maxRequestHeaderSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record MaxHttpResponseHeaderSizeCustomizer(int maxResponseHeaderSize) implements JettyServerCustomizer {
|
||||
|
||||
@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()
|
||||
.setResponseHeaderSize(this.maxResponseHeaderSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,10 +16,6 @@
|
||||
|
||||
package org.springframework.boot.autoconfigure.web.embedded;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.catalina.Lifecycle;
|
||||
import org.apache.catalina.valves.AccessLogValve;
|
||||
import org.apache.catalina.valves.ErrorReportValve;
|
||||
@ -29,7 +25,6 @@ import org.apache.coyote.ProtocolHandler;
|
||||
import org.apache.coyote.UpgradeProtocol;
|
||||
import org.apache.coyote.http11.AbstractHttp11Protocol;
|
||||
import org.apache.coyote.http2.Http2Protocol;
|
||||
|
||||
import org.springframework.boot.autoconfigure.web.ErrorProperties;
|
||||
import org.springframework.boot.autoconfigure.web.ErrorProperties.IncludeAttribute;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
@ -44,6 +39,11 @@ import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.function.ObjIntConsumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Customization for Tomcat-specific features common for both Servlet and Reactive
|
||||
* servers.
|
||||
@ -59,6 +59,8 @@ import org.springframework.util.unit.DataSize;
|
||||
* @author Rafiullah Hamedy
|
||||
* @author Victor Mandujano
|
||||
* @author Parviz Rozikov
|
||||
* @author Florian Storz
|
||||
* @author Michael Weidmann
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class TomcatWebServerFactoryCustomizer
|
||||
@ -95,6 +97,9 @@ public class TomcatWebServerFactoryCustomizer
|
||||
propertyMapper.from(this.serverProperties.getMaxHttpRequestHeaderSize()).whenNonNull().asInt(DataSize::toBytes)
|
||||
.when(this::isPositive)
|
||||
.to((maxHttpRequestHeaderSize) -> customizeMaxHttpRequestHeaderSize(factory, maxHttpRequestHeaderSize));
|
||||
propertyMapper.from(tomcatProperties::getMaxHttpResponseHeaderSize).whenNonNull().asInt(DataSize::toBytes)
|
||||
.when(this::isPositive).to((maxHttpResponseHeaderSize) -> customizeMaxHttpResponseHeaderSize(factory,
|
||||
maxHttpResponseHeaderSize));
|
||||
propertyMapper.from(tomcatProperties::getMaxSwallowSize).whenNonNull().asInt(DataSize::toBytes)
|
||||
.to((maxSwallowSize) -> customizeMaxSwallowSize(factory, maxSwallowSize));
|
||||
propertyMapper.from(tomcatProperties::getMaxHttpFormPostSize).asInt(DataSize::toBytes)
|
||||
@ -129,22 +134,14 @@ public class TomcatWebServerFactoryCustomizer
|
||||
return value > 0;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void customizeAcceptCount(ConfigurableTomcatWebServerFactory factory, int acceptCount) {
|
||||
factory.addConnectorCustomizers((connector) -> {
|
||||
ProtocolHandler handler = connector.getProtocolHandler();
|
||||
if (handler instanceof AbstractProtocol<?> protocol) {
|
||||
protocol.setAcceptCount(acceptCount);
|
||||
}
|
||||
});
|
||||
customizeHandler(factory, acceptCount, AbstractProtocol.class, AbstractProtocol::setAcceptCount);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void customizeProcessorCache(ConfigurableTomcatWebServerFactory factory, int processorCache) {
|
||||
factory.addConnectorCustomizers((connector) -> {
|
||||
ProtocolHandler handler = connector.getProtocolHandler();
|
||||
if (handler instanceof AbstractProtocol) {
|
||||
((AbstractProtocol<?>) handler).setProcessorCache(processorCache);
|
||||
}
|
||||
});
|
||||
customizeHandler(factory, processorCache, AbstractProtocol.class, AbstractProtocol::setProcessorCache);
|
||||
}
|
||||
|
||||
private void customizeKeepAliveTimeout(ConfigurableTomcatWebServerFactory factory, Duration keepAliveTimeout) {
|
||||
@ -161,31 +158,21 @@ public class TomcatWebServerFactoryCustomizer
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void customizeMaxKeepAliveRequests(ConfigurableTomcatWebServerFactory factory, int maxKeepAliveRequests) {
|
||||
factory.addConnectorCustomizers((connector) -> {
|
||||
ProtocolHandler handler = connector.getProtocolHandler();
|
||||
if (handler instanceof AbstractHttp11Protocol<?> protocol) {
|
||||
protocol.setMaxKeepAliveRequests(maxKeepAliveRequests);
|
||||
}
|
||||
});
|
||||
customizeHandler(factory, maxKeepAliveRequests, AbstractHttp11Protocol.class,
|
||||
AbstractHttp11Protocol::setMaxKeepAliveRequests);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void customizeMaxConnections(ConfigurableTomcatWebServerFactory factory, int maxConnections) {
|
||||
factory.addConnectorCustomizers((connector) -> {
|
||||
ProtocolHandler handler = connector.getProtocolHandler();
|
||||
if (handler instanceof AbstractProtocol<?> protocol) {
|
||||
protocol.setMaxConnections(maxConnections);
|
||||
}
|
||||
});
|
||||
customizeHandler(factory, maxConnections, AbstractProtocol.class, AbstractProtocol::setMaxConnections);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void customizeConnectionTimeout(ConfigurableTomcatWebServerFactory factory, Duration connectionTimeout) {
|
||||
factory.addConnectorCustomizers((connector) -> {
|
||||
ProtocolHandler handler = connector.getProtocolHandler();
|
||||
if (handler instanceof AbstractProtocol<?> protocol) {
|
||||
protocol.setConnectionTimeout((int) connectionTimeout.toMillis());
|
||||
}
|
||||
});
|
||||
customizeHandler(factory, (int) connectionTimeout.toMillis(), AbstractProtocol.class,
|
||||
AbstractProtocol::setConnectionTimeout);
|
||||
}
|
||||
|
||||
private void customizeRelaxedPathChars(ConfigurableTomcatWebServerFactory factory, String relaxedChars) {
|
||||
@ -248,40 +235,40 @@ public class TomcatWebServerFactoryCustomizer
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void customizeMaxThreads(ConfigurableTomcatWebServerFactory factory, int maxThreads) {
|
||||
factory.addConnectorCustomizers((connector) -> {
|
||||
ProtocolHandler handler = connector.getProtocolHandler();
|
||||
if (handler instanceof AbstractProtocol protocol) {
|
||||
protocol.setMaxThreads(maxThreads);
|
||||
}
|
||||
});
|
||||
customizeHandler(factory, maxThreads, AbstractProtocol.class, AbstractProtocol::setMaxThreads);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void customizeMinThreads(ConfigurableTomcatWebServerFactory factory, int minSpareThreads) {
|
||||
factory.addConnectorCustomizers((connector) -> {
|
||||
ProtocolHandler handler = connector.getProtocolHandler();
|
||||
if (handler instanceof AbstractProtocol protocol) {
|
||||
protocol.setMinSpareThreads(minSpareThreads);
|
||||
}
|
||||
});
|
||||
customizeHandler(factory, minSpareThreads, AbstractProtocol.class, AbstractProtocol::setMinSpareThreads);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void customizeMaxHttpRequestHeaderSize(ConfigurableTomcatWebServerFactory factory,
|
||||
int maxHttpRequestHeaderSize) {
|
||||
factory.addConnectorCustomizers((connector) -> {
|
||||
ProtocolHandler handler = connector.getProtocolHandler();
|
||||
if (handler instanceof AbstractHttp11Protocol protocol) {
|
||||
protocol.setMaxHttpRequestHeaderSize(maxHttpRequestHeaderSize);
|
||||
}
|
||||
});
|
||||
customizeHandler(factory, maxHttpRequestHeaderSize, AbstractHttp11Protocol.class,
|
||||
AbstractHttp11Protocol::setMaxHttpRequestHeaderSize);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void customizeMaxHttpResponseHeaderSize(ConfigurableTomcatWebServerFactory factory,
|
||||
int maxHttpResponseHeaderSize) {
|
||||
customizeHandler(factory, maxHttpResponseHeaderSize, AbstractHttp11Protocol.class,
|
||||
AbstractHttp11Protocol::setMaxHttpResponseHeaderSize);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void customizeMaxSwallowSize(ConfigurableTomcatWebServerFactory factory, int maxSwallowSize) {
|
||||
customizeHandler(factory, maxSwallowSize, AbstractHttp11Protocol.class,
|
||||
AbstractHttp11Protocol::setMaxSwallowSize);
|
||||
}
|
||||
|
||||
private <T extends ProtocolHandler> void customizeHandler(ConfigurableTomcatWebServerFactory factory, int value,
|
||||
Class<T> type, ObjIntConsumer<T> consumer) {
|
||||
factory.addConnectorCustomizers((connector) -> {
|
||||
ProtocolHandler handler = connector.getProtocolHandler();
|
||||
if (handler instanceof AbstractHttp11Protocol<?> protocol) {
|
||||
protocol.setMaxSwallowSize(maxSwallowSize);
|
||||
if (type.isAssignableFrom(handler.getClass())) {
|
||||
consumer.accept(type.cast(handler), value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -16,15 +16,6 @@
|
||||
|
||||
package org.springframework.boot.autoconfigure.web.embedded;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
|
||||
import org.eclipse.jetty.server.AbstractConnector;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.CustomRequestLog;
|
||||
@ -38,7 +29,6 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.eclipse.jetty.util.thread.ThreadPool;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties.ForwardHeadersStrategy;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties.Jetty;
|
||||
@ -53,6 +43,17 @@ import org.springframework.boot.web.embedded.jetty.JettyWebServer;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.test.context.support.TestPropertySourceUtils;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
@ -286,6 +287,61 @@ class JettyWebServerFactoryCustomizerTests {
|
||||
assertThat(requestHeaderSizes).containsOnly(8192);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeMaxRequestHttpHeaderSize() {
|
||||
bind("server.max-http-request-header-size=2048");
|
||||
JettyWebServer server = customizeAndGetServer();
|
||||
List<Integer> requestHeaderSizes = getRequestHeaderSizes(server);
|
||||
assertThat(requestHeaderSizes).containsOnly(2048);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customMaxHttpRequestHeaderSizeIgnoredIfNegative() {
|
||||
bind("server.max-http-request-header-size=-1");
|
||||
JettyWebServer server = customizeAndGetServer();
|
||||
List<Integer> requestHeaderSizes = getRequestHeaderSizes(server);
|
||||
assertThat(requestHeaderSizes).containsOnly(8192);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customMaxHttpRequestHeaderSizeIgnoredIfZero() {
|
||||
bind("server.max-http-request-header-size=0");
|
||||
JettyWebServer server = customizeAndGetServer();
|
||||
List<Integer> requestHeaderSizes = getRequestHeaderSizes(server);
|
||||
assertThat(requestHeaderSizes).containsOnly(8192);
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultMaxHttpResponseHeaderSize() {
|
||||
JettyWebServer server = customizeAndGetServer();
|
||||
List<Integer> responseHeaderSizes = getResponseHeaderSizes(server);
|
||||
assertThat(responseHeaderSizes).containsOnly(8192);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeMaxHttpResponseHeaderSize() {
|
||||
bind("server.jetty.max-http-response-header-size=2KB");
|
||||
JettyWebServer server = customizeAndGetServer();
|
||||
List<Integer> responseHeaderSizes = getResponseHeaderSizes(server);
|
||||
assertThat(responseHeaderSizes).containsOnly(2048);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customMaxHttpResponseHeaderSizeIgnoredIfNegative() {
|
||||
bind("server.jetty.max-http-response-header-size=-1");
|
||||
JettyWebServer server = customizeAndGetServer();
|
||||
List<Integer> responseHeaderSizes = getResponseHeaderSizes(server);
|
||||
assertThat(responseHeaderSizes).containsOnly(8192);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customMaxHttpResponseHeaderSizeIgnoredIfZero() {
|
||||
bind("server.jetty.max-http-response-header-size=0");
|
||||
JettyWebServer server = customizeAndGetServer();
|
||||
List<Integer> responseHeaderSizes = getResponseHeaderSizes(server);
|
||||
assertThat(responseHeaderSizes).containsOnly(8192);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customIdleTimeout() {
|
||||
bind("server.jetty.connection-idle-timeout=60s");
|
||||
@ -303,6 +359,14 @@ class JettyWebServerFactoryCustomizerTests {
|
||||
}
|
||||
|
||||
private List<Integer> getRequestHeaderSizes(JettyWebServer server) {
|
||||
return getHeaderSizes(server, HttpConfiguration::getRequestHeaderSize);
|
||||
}
|
||||
|
||||
private List<Integer> getResponseHeaderSizes(JettyWebServer server) {
|
||||
return getHeaderSizes(server, HttpConfiguration::getResponseHeaderSize);
|
||||
}
|
||||
|
||||
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();
|
||||
@ -313,7 +377,7 @@ class JettyWebServerFactoryCustomizerTests {
|
||||
.forEach((cf) -> {
|
||||
ConnectionFactory factory = (ConnectionFactory) cf;
|
||||
HttpConfiguration configuration = factory.getHttpConfiguration();
|
||||
requestHeaderSizes.add(configuration.getRequestHeaderSize());
|
||||
requestHeaderSizes.add(provider.apply(configuration));
|
||||
});
|
||||
}
|
||||
return requestHeaderSizes;
|
||||
|
@ -211,6 +211,68 @@ class TomcatWebServerFactoryCustomizerTests {
|
||||
.getMaxHttpRequestHeaderSize()).isEqualTo(DataSize.ofKilobytes(8).toBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultMaxHttpRequestHeaderSize() {
|
||||
customizeAndRunServer((server) -> assertThat(
|
||||
((AbstractHttp11Protocol<?>) server.getTomcat().getConnector().getProtocolHandler())
|
||||
.getMaxHttpRequestHeaderSize()).isEqualTo(DataSize.ofKilobytes(8).toBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customMaxHttpRequestHeaderSize() {
|
||||
bind("server.max-http-request-header-size=10MB");
|
||||
customizeAndRunServer((server) -> assertThat(
|
||||
((AbstractHttp11Protocol<?>) server.getTomcat().getConnector().getProtocolHandler())
|
||||
.getMaxHttpRequestHeaderSize()).isEqualTo(DataSize.ofMegabytes(10).toBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customMaxRequestHttpHeaderSizeIgnoredIfNegative() {
|
||||
bind("server.max-http-request-header-size=-1");
|
||||
customizeAndRunServer((server) -> assertThat(
|
||||
((AbstractHttp11Protocol<?>) server.getTomcat().getConnector().getProtocolHandler())
|
||||
.getMaxHttpRequestHeaderSize()).isEqualTo(DataSize.ofKilobytes(8).toBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customMaxRequestHttpHeaderSizeIgnoredIfZero() {
|
||||
bind("server.max-http-request-header-size=0");
|
||||
customizeAndRunServer((server) -> assertThat(
|
||||
((AbstractHttp11Protocol<?>) server.getTomcat().getConnector().getProtocolHandler())
|
||||
.getMaxHttpRequestHeaderSize()).isEqualTo(DataSize.ofKilobytes(8).toBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultMaxHttpResponseHeaderSize() {
|
||||
customizeAndRunServer((server) -> assertThat(
|
||||
((AbstractHttp11Protocol<?>) server.getTomcat().getConnector().getProtocolHandler())
|
||||
.getMaxHttpResponseHeaderSize()).isEqualTo(DataSize.ofKilobytes(8).toBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customMaxHttpResponseHeaderSize() {
|
||||
bind("server.tomcat.max-http-response-header-size=10MB");
|
||||
customizeAndRunServer((server) -> assertThat(
|
||||
((AbstractHttp11Protocol<?>) server.getTomcat().getConnector().getProtocolHandler())
|
||||
.getMaxHttpResponseHeaderSize()).isEqualTo(DataSize.ofMegabytes(10).toBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customMaxResponseHttpHeaderSizeIgnoredIfNegative() {
|
||||
bind("server.tomcat.max-http-response-header-size=-1");
|
||||
customizeAndRunServer((server) -> assertThat(
|
||||
((AbstractHttp11Protocol<?>) server.getTomcat().getConnector().getProtocolHandler())
|
||||
.getMaxHttpResponseHeaderSize()).isEqualTo(DataSize.ofKilobytes(8).toBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customMaxResponseHttpHeaderSizeIgnoredIfZero() {
|
||||
bind("server.tomcat.max-http-response-header-size=0");
|
||||
customizeAndRunServer((server) -> assertThat(
|
||||
((AbstractHttp11Protocol<?>) server.getTomcat().getConnector().getProtocolHandler())
|
||||
.getMaxHttpResponseHeaderSize()).isEqualTo(DataSize.ofKilobytes(8).toBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customMaxSwallowSize() {
|
||||
bind("server.tomcat.max-swallow-size=10MB");
|
||||
|
@ -0,0 +1,26 @@
|
||||
package smoketest.jetty.service;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import smoketest.jetty.util.RandomStringUtil;
|
||||
|
||||
@Component
|
||||
public class HttpHeaderService {
|
||||
|
||||
@Value("${server.jetty.max-http-response-header-size}")
|
||||
private int maxHttpResponseHeaderSize;
|
||||
|
||||
/**
|
||||
* generate a random byte array that
|
||||
* <ol>
|
||||
* <li>is longer than configured
|
||||
* <code>server.jetty.max-http-response-header-size</code></li>
|
||||
* <li>is url encoded by base 64 encode the random value</li>
|
||||
* </ol>
|
||||
* @return a base64 encoded string of random bytes
|
||||
*/
|
||||
public String getHeaderValue() {
|
||||
return RandomStringUtil.getRandomBase64EncodedString(maxHttpResponseHeaderSize + 1);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package smoketest.jetty.util;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Random;
|
||||
|
||||
public class RandomStringUtil {
|
||||
|
||||
private RandomStringUtil() {
|
||||
}
|
||||
|
||||
public static String getRandomBase64EncodedString(int length) {
|
||||
byte[] responseHeader = new byte[length];
|
||||
new Random().nextBytes(responseHeader);
|
||||
return Base64.getEncoder().encodeToString(responseHeader);
|
||||
}
|
||||
|
||||
}
|
@ -16,7 +16,9 @@
|
||||
|
||||
package smoketest.jetty.web;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import smoketest.jetty.service.HelloWorldService;
|
||||
import smoketest.jetty.service.HttpHeaderService;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
@ -29,10 +31,21 @@ public class SampleController {
|
||||
@Autowired
|
||||
private HelloWorldService helloWorldService;
|
||||
|
||||
@Autowired
|
||||
private HttpHeaderService httpHeaderService;
|
||||
|
||||
@GetMapping("/")
|
||||
@ResponseBody
|
||||
public String helloWorld() {
|
||||
return this.helloWorldService.getHelloMessage();
|
||||
}
|
||||
|
||||
@GetMapping("/max-http-response-header")
|
||||
@ResponseBody
|
||||
public String maxHttpResponseHeader(HttpServletResponse response) {
|
||||
String headerValue = httpHeaderService.getHeaderValue();
|
||||
response.addHeader("x-max-header", headerValue);
|
||||
return this.helloWorldService.getHelloMessage();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
server.compression.enabled: true
|
||||
server.compression.min-response-size: 1
|
||||
server.max-http-request-header-size=1000
|
||||
server.jetty.threads.acceptors=2
|
||||
server.jetty.max-http-response-header-size=1000
|
||||
|
@ -22,9 +22,13 @@ import java.util.zip.GZIPInputStream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.boot.test.system.CapturedOutput;
|
||||
import org.springframework.boot.test.system.OutputCaptureExtension;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@ -32,6 +36,7 @@ import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import smoketest.jetty.util.RandomStringUtil;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ -40,13 +45,19 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
* @author Florian Storz
|
||||
* @author Michael Weidmann
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class SampleJettyApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate restTemplate;
|
||||
|
||||
@Value("${server.max-http-request-header-size}")
|
||||
private int maxHttpRequestHeaderSize;
|
||||
|
||||
@Test
|
||||
void testHome() {
|
||||
ResponseEntity<String> entity = this.restTemplate.getForEntity("/", String.class);
|
||||
@ -66,4 +77,22 @@ class SampleJettyApplicationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMaxHttpResponseHeaderSize(CapturedOutput output) {
|
||||
ResponseEntity<String> entity = this.restTemplate.getForEntity("/max-http-response-header", String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
assertThat(output).contains(
|
||||
"org.eclipse.jetty.server.HttpChannel : handleException /max-http-response-header org.eclipse.jetty.http.BadMessageException: 500: Response header too large");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMaxHttpRequestHeaderSize() {
|
||||
String headerValue = RandomStringUtil.getRandomBase64EncodedString(maxHttpRequestHeaderSize + 1);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("x-max-request-header", headerValue);
|
||||
HttpEntity<?> httpEntity = new HttpEntity<>(headers);
|
||||
ResponseEntity<String> entity = this.restTemplate.exchange("/", HttpMethod.GET, httpEntity, String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
package smoketest.tomcat.service;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import smoketest.tomcat.util.RandomStringUtil;
|
||||
|
||||
@Component
|
||||
public class HttpHeaderService {
|
||||
|
||||
@Value("${server.tomcat.max-http-response-header-size}")
|
||||
private int maxHttpResponseHeaderSize;
|
||||
|
||||
/**
|
||||
* generate a random byte array that
|
||||
* <ol>
|
||||
* <li>is longer than configured
|
||||
* <code>server.jetty.max-http-response-header-size</code></li>
|
||||
* <li>is url encoded by base 64 encode the random value</li>
|
||||
* </ol>
|
||||
* @return a base64 encoded string of random bytes
|
||||
*/
|
||||
public String getHeaderValue() {
|
||||
return RandomStringUtil.getRandomBase64EncodedString(maxHttpResponseHeaderSize + 1);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package smoketest.tomcat.util;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Random;
|
||||
|
||||
public class RandomStringUtil {
|
||||
|
||||
private RandomStringUtil() {
|
||||
}
|
||||
|
||||
public static String getRandomBase64EncodedString(int length) {
|
||||
byte[] responseHeader = new byte[length];
|
||||
new Random().nextBytes(responseHeader);
|
||||
return Base64.getEncoder().encodeToString(responseHeader);
|
||||
}
|
||||
|
||||
}
|
@ -16,12 +16,14 @@
|
||||
|
||||
package smoketest.tomcat.web;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import smoketest.tomcat.service.HelloWorldService;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import smoketest.tomcat.service.HttpHeaderService;
|
||||
|
||||
@Controller
|
||||
public class SampleController {
|
||||
@ -29,10 +31,21 @@ public class SampleController {
|
||||
@Autowired
|
||||
private HelloWorldService helloWorldService;
|
||||
|
||||
@Autowired
|
||||
private HttpHeaderService httpHeaderService;
|
||||
|
||||
@GetMapping("/")
|
||||
@ResponseBody
|
||||
public String helloWorld() {
|
||||
return this.helloWorldService.getHelloMessage();
|
||||
}
|
||||
|
||||
@GetMapping("/max-http-response-header")
|
||||
@ResponseBody
|
||||
public String maxHttpResponseHeader(HttpServletResponse response) {
|
||||
String headerValue = httpHeaderService.getHeaderValue();
|
||||
response.addHeader("x-max-header", headerValue);
|
||||
return this.helloWorldService.getHelloMessage();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
server.compression.enabled: true
|
||||
server.compression.min-response-size: 1
|
||||
server.max-http-request-header-size=1000
|
||||
server.tomcat.connection-timeout=5s
|
||||
server.tomcat.max-http-response-header-size=1000
|
||||
|
@ -24,9 +24,13 @@ import org.apache.coyote.AbstractProtocol;
|
||||
import org.apache.coyote.ProtocolHandler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.boot.test.system.CapturedOutput;
|
||||
import org.springframework.boot.test.system.OutputCaptureExtension;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
|
||||
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
|
||||
@ -37,6 +41,7 @@ import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import smoketest.tomcat.util.RandomStringUtil;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ -45,8 +50,11 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
* @author Florian Storz
|
||||
* @author Michael Weidmann
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class SampleTomcatApplicationTests {
|
||||
|
||||
@Autowired
|
||||
@ -55,6 +63,9 @@ class SampleTomcatApplicationTests {
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Value("${server.max-http-request-header-size}")
|
||||
private int maxHttpRequestHeaderSize;
|
||||
|
||||
@Test
|
||||
void testHome() {
|
||||
ResponseEntity<String> entity = this.restTemplate.getForEntity("/", String.class);
|
||||
@ -83,4 +94,23 @@ class SampleTomcatApplicationTests {
|
||||
assertThat(timeout).isEqualTo(5000);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMaxHttpResponseHeaderSize(CapturedOutput output) {
|
||||
ResponseEntity<String> entity = this.restTemplate.getForEntity("/max-http-response-header", String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
assertThat(output).contains(
|
||||
"threw exception [Request processing failed: org.apache.coyote.http11.HeadersTooLargeException: An attempt was made to write more data to the response headers than there was room available in the buffer. Increase maxHttpHeaderSize on the connector or write less data into the response headers.]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMaxHttpRequestHeaderSize(CapturedOutput output) {
|
||||
String headerValue = RandomStringUtil.getRandomBase64EncodedString(maxHttpRequestHeaderSize + 1);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("x-max-request-header", headerValue);
|
||||
HttpEntity<?> httpEntity = new HttpEntity<>(headers);
|
||||
ResponseEntity<String> entity = this.restTemplate.exchange("/", HttpMethod.GET, httpEntity, String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
assertThat(output).contains("java.lang.IllegalArgumentException: Request header is too large");
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user