mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-07-15 01:07:30 +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 Victor Mandujano
|
||||||
* @author Chris Bono
|
* @author Chris Bono
|
||||||
* @author Parviz Rozikov
|
* @author Parviz Rozikov
|
||||||
|
* @author Florian Storz
|
||||||
|
* @author Michael Weidmann
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
|
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
|
||||||
@ -490,6 +492,11 @@ public class ServerProperties {
|
|||||||
*/
|
*/
|
||||||
private final Remoteip remoteip = new Remoteip();
|
private final Remoteip remoteip = new Remoteip();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum size of the HTTP response header.
|
||||||
|
*/
|
||||||
|
private DataSize maxHttpResponseHeaderSize = DataSize.ofKilobytes(8);
|
||||||
|
|
||||||
public DataSize getMaxHttpFormPostSize() {
|
public DataSize getMaxHttpFormPostSize() {
|
||||||
return this.maxHttpFormPostSize;
|
return this.maxHttpFormPostSize;
|
||||||
}
|
}
|
||||||
@ -646,6 +653,14 @@ public class ServerProperties {
|
|||||||
return this.remoteip;
|
return this.remoteip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DataSize getMaxHttpResponseHeaderSize() {
|
||||||
|
return maxHttpResponseHeaderSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxHttpResponseHeaderSize(DataSize maxHttpResponseHeaderSize) {
|
||||||
|
this.maxHttpResponseHeaderSize = maxHttpResponseHeaderSize;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tomcat access log properties.
|
* Tomcat access log properties.
|
||||||
*/
|
*/
|
||||||
@ -1096,6 +1111,11 @@ public class ServerProperties {
|
|||||||
*/
|
*/
|
||||||
private Duration connectionIdleTimeout;
|
private Duration connectionIdleTimeout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum size of the HTTP response header.
|
||||||
|
*/
|
||||||
|
private DataSize maxHttpResponseHeaderSize = DataSize.ofKilobytes(8);
|
||||||
|
|
||||||
public Accesslog getAccesslog() {
|
public Accesslog getAccesslog() {
|
||||||
return this.accesslog;
|
return this.accesslog;
|
||||||
}
|
}
|
||||||
@ -1120,6 +1140,14 @@ public class ServerProperties {
|
|||||||
this.connectionIdleTimeout = connectionIdleTimeout;
|
this.connectionIdleTimeout = connectionIdleTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DataSize getMaxHttpResponseHeaderSize() {
|
||||||
|
return maxHttpResponseHeaderSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxHttpResponseHeaderSize(DataSize maxHttpResponseHeaderSize) {
|
||||||
|
this.maxHttpResponseHeaderSize = maxHttpResponseHeaderSize;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Jetty access log properties.
|
* Jetty access log properties.
|
||||||
*/
|
*/
|
||||||
|
@ -53,6 +53,8 @@ import org.springframework.util.unit.DataSize;
|
|||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @author HaiTao Zhang
|
* @author HaiTao Zhang
|
||||||
* @author Rafiullah Hamedy
|
* @author Rafiullah Hamedy
|
||||||
|
* @author Florian Storz
|
||||||
|
* @author Michael Weidmann
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class JettyWebServerFactoryCustomizer
|
public class JettyWebServerFactoryCustomizer
|
||||||
@ -85,6 +87,9 @@ public class JettyWebServerFactoryCustomizer
|
|||||||
propertyMapper.from(properties::getMaxHttpRequestHeaderSize).whenNonNull().asInt(DataSize::toBytes)
|
propertyMapper.from(properties::getMaxHttpRequestHeaderSize).whenNonNull().asInt(DataSize::toBytes)
|
||||||
.when(this::isPositive).to((maxHttpRequestHeaderSize) -> factory
|
.when(this::isPositive).to((maxHttpRequestHeaderSize) -> factory
|
||||||
.addServerCustomizers(new MaxHttpRequestHeaderSizeCustomizer(maxHttpRequestHeaderSize)));
|
.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)
|
propertyMapper.from(jettyProperties::getMaxHttpFormPostSize).asInt(DataSize::toBytes).when(this::isPositive)
|
||||||
.to((maxHttpFormPostSize) -> customizeMaxHttpFormPostSize(factory, maxHttpFormPostSize));
|
.to((maxHttpFormPostSize) -> customizeMaxHttpFormPostSize(factory, maxHttpFormPostSize));
|
||||||
propertyMapper.from(jettyProperties::getConnectionIdleTimeout).whenNonNull()
|
propertyMapper.from(jettyProperties::getConnectionIdleTimeout).whenNonNull()
|
||||||
@ -192,13 +197,7 @@ public class JettyWebServerFactoryCustomizer
|
|||||||
return CustomRequestLog.NCSA_FORMAT;
|
return CustomRequestLog.NCSA_FORMAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MaxHttpRequestHeaderSizeCustomizer implements JettyServerCustomizer {
|
private record MaxHttpRequestHeaderSizeCustomizer(int maxRequestHeaderSize) implements JettyServerCustomizer {
|
||||||
|
|
||||||
private final int maxRequestHeaderSize;
|
|
||||||
|
|
||||||
MaxHttpRequestHeaderSizeCustomizer(int maxRequestHeaderSize) {
|
|
||||||
this.maxRequestHeaderSize = maxRequestHeaderSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void customize(Server server) {
|
public void customize(Server server) {
|
||||||
@ -215,7 +214,25 @@ public class JettyWebServerFactoryCustomizer
|
|||||||
.setRequestHeaderSize(this.maxRequestHeaderSize);
|
.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;
|
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.Lifecycle;
|
||||||
import org.apache.catalina.valves.AccessLogValve;
|
import org.apache.catalina.valves.AccessLogValve;
|
||||||
import org.apache.catalina.valves.ErrorReportValve;
|
import org.apache.catalina.valves.ErrorReportValve;
|
||||||
@ -29,7 +25,6 @@ import org.apache.coyote.ProtocolHandler;
|
|||||||
import org.apache.coyote.UpgradeProtocol;
|
import org.apache.coyote.UpgradeProtocol;
|
||||||
import org.apache.coyote.http11.AbstractHttp11Protocol;
|
import org.apache.coyote.http11.AbstractHttp11Protocol;
|
||||||
import org.apache.coyote.http2.Http2Protocol;
|
import org.apache.coyote.http2.Http2Protocol;
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.web.ErrorProperties;
|
import org.springframework.boot.autoconfigure.web.ErrorProperties;
|
||||||
import org.springframework.boot.autoconfigure.web.ErrorProperties.IncludeAttribute;
|
import org.springframework.boot.autoconfigure.web.ErrorProperties.IncludeAttribute;
|
||||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
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.StringUtils;
|
||||||
import org.springframework.util.unit.DataSize;
|
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
|
* Customization for Tomcat-specific features common for both Servlet and Reactive
|
||||||
* servers.
|
* servers.
|
||||||
@ -59,6 +59,8 @@ import org.springframework.util.unit.DataSize;
|
|||||||
* @author Rafiullah Hamedy
|
* @author Rafiullah Hamedy
|
||||||
* @author Victor Mandujano
|
* @author Victor Mandujano
|
||||||
* @author Parviz Rozikov
|
* @author Parviz Rozikov
|
||||||
|
* @author Florian Storz
|
||||||
|
* @author Michael Weidmann
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class TomcatWebServerFactoryCustomizer
|
public class TomcatWebServerFactoryCustomizer
|
||||||
@ -95,6 +97,9 @@ public class TomcatWebServerFactoryCustomizer
|
|||||||
propertyMapper.from(this.serverProperties.getMaxHttpRequestHeaderSize()).whenNonNull().asInt(DataSize::toBytes)
|
propertyMapper.from(this.serverProperties.getMaxHttpRequestHeaderSize()).whenNonNull().asInt(DataSize::toBytes)
|
||||||
.when(this::isPositive)
|
.when(this::isPositive)
|
||||||
.to((maxHttpRequestHeaderSize) -> customizeMaxHttpRequestHeaderSize(factory, maxHttpRequestHeaderSize));
|
.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)
|
propertyMapper.from(tomcatProperties::getMaxSwallowSize).whenNonNull().asInt(DataSize::toBytes)
|
||||||
.to((maxSwallowSize) -> customizeMaxSwallowSize(factory, maxSwallowSize));
|
.to((maxSwallowSize) -> customizeMaxSwallowSize(factory, maxSwallowSize));
|
||||||
propertyMapper.from(tomcatProperties::getMaxHttpFormPostSize).asInt(DataSize::toBytes)
|
propertyMapper.from(tomcatProperties::getMaxHttpFormPostSize).asInt(DataSize::toBytes)
|
||||||
@ -129,22 +134,14 @@ public class TomcatWebServerFactoryCustomizer
|
|||||||
return value > 0;
|
return value > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
private void customizeAcceptCount(ConfigurableTomcatWebServerFactory factory, int acceptCount) {
|
private void customizeAcceptCount(ConfigurableTomcatWebServerFactory factory, int acceptCount) {
|
||||||
factory.addConnectorCustomizers((connector) -> {
|
customizeHandler(factory, acceptCount, AbstractProtocol.class, AbstractProtocol::setAcceptCount);
|
||||||
ProtocolHandler handler = connector.getProtocolHandler();
|
|
||||||
if (handler instanceof AbstractProtocol<?> protocol) {
|
|
||||||
protocol.setAcceptCount(acceptCount);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
private void customizeProcessorCache(ConfigurableTomcatWebServerFactory factory, int processorCache) {
|
private void customizeProcessorCache(ConfigurableTomcatWebServerFactory factory, int processorCache) {
|
||||||
factory.addConnectorCustomizers((connector) -> {
|
customizeHandler(factory, processorCache, AbstractProtocol.class, AbstractProtocol::setProcessorCache);
|
||||||
ProtocolHandler handler = connector.getProtocolHandler();
|
|
||||||
if (handler instanceof AbstractProtocol) {
|
|
||||||
((AbstractProtocol<?>) handler).setProcessorCache(processorCache);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void customizeKeepAliveTimeout(ConfigurableTomcatWebServerFactory factory, Duration keepAliveTimeout) {
|
private void customizeKeepAliveTimeout(ConfigurableTomcatWebServerFactory factory, Duration keepAliveTimeout) {
|
||||||
@ -161,31 +158,21 @@ public class TomcatWebServerFactoryCustomizer
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
private void customizeMaxKeepAliveRequests(ConfigurableTomcatWebServerFactory factory, int maxKeepAliveRequests) {
|
private void customizeMaxKeepAliveRequests(ConfigurableTomcatWebServerFactory factory, int maxKeepAliveRequests) {
|
||||||
factory.addConnectorCustomizers((connector) -> {
|
customizeHandler(factory, maxKeepAliveRequests, AbstractHttp11Protocol.class,
|
||||||
ProtocolHandler handler = connector.getProtocolHandler();
|
AbstractHttp11Protocol::setMaxKeepAliveRequests);
|
||||||
if (handler instanceof AbstractHttp11Protocol<?> protocol) {
|
|
||||||
protocol.setMaxKeepAliveRequests(maxKeepAliveRequests);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
private void customizeMaxConnections(ConfigurableTomcatWebServerFactory factory, int maxConnections) {
|
private void customizeMaxConnections(ConfigurableTomcatWebServerFactory factory, int maxConnections) {
|
||||||
factory.addConnectorCustomizers((connector) -> {
|
customizeHandler(factory, maxConnections, AbstractProtocol.class, AbstractProtocol::setMaxConnections);
|
||||||
ProtocolHandler handler = connector.getProtocolHandler();
|
|
||||||
if (handler instanceof AbstractProtocol<?> protocol) {
|
|
||||||
protocol.setMaxConnections(maxConnections);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
private void customizeConnectionTimeout(ConfigurableTomcatWebServerFactory factory, Duration connectionTimeout) {
|
private void customizeConnectionTimeout(ConfigurableTomcatWebServerFactory factory, Duration connectionTimeout) {
|
||||||
factory.addConnectorCustomizers((connector) -> {
|
customizeHandler(factory, (int) connectionTimeout.toMillis(), AbstractProtocol.class,
|
||||||
ProtocolHandler handler = connector.getProtocolHandler();
|
AbstractProtocol::setConnectionTimeout);
|
||||||
if (handler instanceof AbstractProtocol<?> protocol) {
|
|
||||||
protocol.setConnectionTimeout((int) connectionTimeout.toMillis());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void customizeRelaxedPathChars(ConfigurableTomcatWebServerFactory factory, String relaxedChars) {
|
private void customizeRelaxedPathChars(ConfigurableTomcatWebServerFactory factory, String relaxedChars) {
|
||||||
@ -248,40 +235,40 @@ public class TomcatWebServerFactoryCustomizer
|
|||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
private void customizeMaxThreads(ConfigurableTomcatWebServerFactory factory, int maxThreads) {
|
private void customizeMaxThreads(ConfigurableTomcatWebServerFactory factory, int maxThreads) {
|
||||||
factory.addConnectorCustomizers((connector) -> {
|
customizeHandler(factory, maxThreads, AbstractProtocol.class, AbstractProtocol::setMaxThreads);
|
||||||
ProtocolHandler handler = connector.getProtocolHandler();
|
|
||||||
if (handler instanceof AbstractProtocol protocol) {
|
|
||||||
protocol.setMaxThreads(maxThreads);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
private void customizeMinThreads(ConfigurableTomcatWebServerFactory factory, int minSpareThreads) {
|
private void customizeMinThreads(ConfigurableTomcatWebServerFactory factory, int minSpareThreads) {
|
||||||
factory.addConnectorCustomizers((connector) -> {
|
customizeHandler(factory, minSpareThreads, AbstractProtocol.class, AbstractProtocol::setMinSpareThreads);
|
||||||
ProtocolHandler handler = connector.getProtocolHandler();
|
|
||||||
if (handler instanceof AbstractProtocol protocol) {
|
|
||||||
protocol.setMinSpareThreads(minSpareThreads);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
private void customizeMaxHttpRequestHeaderSize(ConfigurableTomcatWebServerFactory factory,
|
private void customizeMaxHttpRequestHeaderSize(ConfigurableTomcatWebServerFactory factory,
|
||||||
int maxHttpRequestHeaderSize) {
|
int maxHttpRequestHeaderSize) {
|
||||||
factory.addConnectorCustomizers((connector) -> {
|
customizeHandler(factory, maxHttpRequestHeaderSize, AbstractHttp11Protocol.class,
|
||||||
ProtocolHandler handler = connector.getProtocolHandler();
|
AbstractHttp11Protocol::setMaxHttpRequestHeaderSize);
|
||||||
if (handler instanceof AbstractHttp11Protocol protocol) {
|
|
||||||
protocol.setMaxHttpRequestHeaderSize(maxHttpRequestHeaderSize);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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) {
|
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) -> {
|
factory.addConnectorCustomizers((connector) -> {
|
||||||
ProtocolHandler handler = connector.getProtocolHandler();
|
ProtocolHandler handler = connector.getProtocolHandler();
|
||||||
if (handler instanceof AbstractHttp11Protocol<?> protocol) {
|
if (type.isAssignableFrom(handler.getClass())) {
|
||||||
protocol.setMaxSwallowSize(maxSwallowSize);
|
consumer.accept(type.cast(handler), value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -16,15 +16,6 @@
|
|||||||
|
|
||||||
package org.springframework.boot.autoconfigure.web.embedded;
|
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.AbstractConnector;
|
||||||
import org.eclipse.jetty.server.Connector;
|
import org.eclipse.jetty.server.Connector;
|
||||||
import org.eclipse.jetty.server.CustomRequestLog;
|
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.eclipse.jetty.util.thread.ThreadPool;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||||
import org.springframework.boot.autoconfigure.web.ServerProperties.ForwardHeadersStrategy;
|
import org.springframework.boot.autoconfigure.web.ServerProperties.ForwardHeadersStrategy;
|
||||||
import org.springframework.boot.autoconfigure.web.ServerProperties.Jetty;
|
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.mock.env.MockEnvironment;
|
||||||
import org.springframework.test.context.support.TestPropertySourceUtils;
|
import org.springframework.test.context.support.TestPropertySourceUtils;
|
||||||
import org.springframework.test.util.ReflectionTestUtils;
|
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.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.BDDMockito.then;
|
import static org.mockito.BDDMockito.then;
|
||||||
@ -286,6 +287,61 @@ class JettyWebServerFactoryCustomizerTests {
|
|||||||
assertThat(requestHeaderSizes).containsOnly(8192);
|
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
|
@Test
|
||||||
void customIdleTimeout() {
|
void customIdleTimeout() {
|
||||||
bind("server.jetty.connection-idle-timeout=60s");
|
bind("server.jetty.connection-idle-timeout=60s");
|
||||||
@ -303,6 +359,14 @@ class JettyWebServerFactoryCustomizerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<Integer> getRequestHeaderSizes(JettyWebServer server) {
|
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<>();
|
List<Integer> requestHeaderSizes = new ArrayList<>();
|
||||||
// Start (and directly stop) server to have connectors available
|
// Start (and directly stop) server to have connectors available
|
||||||
server.start();
|
server.start();
|
||||||
@ -313,7 +377,7 @@ class JettyWebServerFactoryCustomizerTests {
|
|||||||
.forEach((cf) -> {
|
.forEach((cf) -> {
|
||||||
ConnectionFactory factory = (ConnectionFactory) cf;
|
ConnectionFactory factory = (ConnectionFactory) cf;
|
||||||
HttpConfiguration configuration = factory.getHttpConfiguration();
|
HttpConfiguration configuration = factory.getHttpConfiguration();
|
||||||
requestHeaderSizes.add(configuration.getRequestHeaderSize());
|
requestHeaderSizes.add(provider.apply(configuration));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return requestHeaderSizes;
|
return requestHeaderSizes;
|
||||||
|
@ -211,6 +211,68 @@ class TomcatWebServerFactoryCustomizerTests {
|
|||||||
.getMaxHttpRequestHeaderSize()).isEqualTo(DataSize.ofKilobytes(8).toBytes()));
|
.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
|
@Test
|
||||||
void customMaxSwallowSize() {
|
void customMaxSwallowSize() {
|
||||||
bind("server.tomcat.max-swallow-size=10MB");
|
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;
|
package smoketest.jetty.web;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import smoketest.jetty.service.HelloWorldService;
|
import smoketest.jetty.service.HelloWorldService;
|
||||||
|
import smoketest.jetty.service.HttpHeaderService;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
@ -29,10 +31,21 @@ public class SampleController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private HelloWorldService helloWorldService;
|
private HelloWorldService helloWorldService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private HttpHeaderService httpHeaderService;
|
||||||
|
|
||||||
@GetMapping("/")
|
@GetMapping("/")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public String helloWorld() {
|
public String helloWorld() {
|
||||||
return this.helloWorldService.getHelloMessage();
|
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.enabled: true
|
||||||
server.compression.min-response-size: 1
|
server.compression.min-response-size: 1
|
||||||
|
server.max-http-request-header-size=1000
|
||||||
server.jetty.threads.acceptors=2
|
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.Test;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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;
|
||||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
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.test.web.client.TestRestTemplate;
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
@ -32,6 +36,7 @@ import org.springframework.http.HttpMethod;
|
|||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.util.StreamUtils;
|
import org.springframework.util.StreamUtils;
|
||||||
|
import smoketest.jetty.util.RandomStringUtil;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@ -40,13 +45,19 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
*
|
*
|
||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
|
* @author Florian Storz
|
||||||
|
* @author Michael Weidmann
|
||||||
*/
|
*/
|
||||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||||
|
@ExtendWith(OutputCaptureExtension.class)
|
||||||
class SampleJettyApplicationTests {
|
class SampleJettyApplicationTests {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private TestRestTemplate restTemplate;
|
private TestRestTemplate restTemplate;
|
||||||
|
|
||||||
|
@Value("${server.max-http-request-header-size}")
|
||||||
|
private int maxHttpRequestHeaderSize;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHome() {
|
void testHome() {
|
||||||
ResponseEntity<String> entity = this.restTemplate.getForEntity("/", String.class);
|
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;
|
package smoketest.tomcat.web;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import smoketest.tomcat.service.HelloWorldService;
|
import smoketest.tomcat.service.HelloWorldService;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
import smoketest.tomcat.service.HttpHeaderService;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class SampleController {
|
public class SampleController {
|
||||||
@ -29,10 +31,21 @@ public class SampleController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private HelloWorldService helloWorldService;
|
private HelloWorldService helloWorldService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private HttpHeaderService httpHeaderService;
|
||||||
|
|
||||||
@GetMapping("/")
|
@GetMapping("/")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public String helloWorld() {
|
public String helloWorld() {
|
||||||
return this.helloWorldService.getHelloMessage();
|
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.enabled: true
|
||||||
server.compression.min-response-size: 1
|
server.compression.min-response-size: 1
|
||||||
|
server.max-http-request-header-size=1000
|
||||||
server.tomcat.connection-timeout=5s
|
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.apache.coyote.ProtocolHandler;
|
||||||
import org.junit.jupiter.api.Test;
|
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.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
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.test.web.client.TestRestTemplate;
|
||||||
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
|
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
|
||||||
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
|
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.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.util.StreamUtils;
|
import org.springframework.util.StreamUtils;
|
||||||
|
import smoketest.tomcat.util.RandomStringUtil;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@ -45,8 +50,11 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
*
|
*
|
||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
|
* @author Florian Storz
|
||||||
|
* @author Michael Weidmann
|
||||||
*/
|
*/
|
||||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||||
|
@ExtendWith(OutputCaptureExtension.class)
|
||||||
class SampleTomcatApplicationTests {
|
class SampleTomcatApplicationTests {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -55,6 +63,9 @@ class SampleTomcatApplicationTests {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ApplicationContext applicationContext;
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
@Value("${server.max-http-request-header-size}")
|
||||||
|
private int maxHttpRequestHeaderSize;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHome() {
|
void testHome() {
|
||||||
ResponseEntity<String> entity = this.restTemplate.getForEntity("/", String.class);
|
ResponseEntity<String> entity = this.restTemplate.getForEntity("/", String.class);
|
||||||
@ -83,4 +94,23 @@ class SampleTomcatApplicationTests {
|
|||||||
assertThat(timeout).isEqualTo(5000);
|
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