ARTEMIS-4939 allow config of header sizes for embedded web server

This commit is contained in:
Justin Bertram 2024-07-19 12:45:50 -05:00
parent ef3b5fa02c
commit 0ecbae6108
4 changed files with 93 additions and 0 deletions

View File

@ -107,6 +107,12 @@ public class WebServerDTO extends ComponentDTO {
@XmlAttribute
public Integer scanPeriod;
@XmlAttribute
public Integer maxRequestHeaderSize;
@XmlAttribute
public Integer maxResponseHeaderSize;
public String getPath() {
return path;
}

View File

@ -136,6 +136,14 @@ public class WebServerComponent implements ExternalComponent, WebServerComponent
HttpConfiguration httpConfiguration = new HttpConfiguration();
if (webServerConfig.maxRequestHeaderSize != null) {
httpConfiguration.setRequestHeaderSize(webServerConfig.maxRequestHeaderSize);
}
if (webServerConfig.maxResponseHeaderSize != null) {
httpConfiguration.setResponseHeaderSize(webServerConfig.maxResponseHeaderSize);
}
if (this.webServerConfig.customizer != null) {
try {
httpConfiguration.addCustomizer((HttpConfiguration.Customizer) ClassloadingUtil.getInstanceWithTypeCheck(this.webServerConfig.customizer, HttpConfiguration.Customizer.class, this.getClass().getClassLoader()));

View File

@ -45,6 +45,14 @@ idleThreadTimeout::
The time to wait before terminating an idle thread from the embedded web server. Measured in milliseconds. Default is `60000`.
scanPeriod::
How often to scan for changes of the key and trust store files related to a binding when the `sslAutoReload` attribute value of the `binding` element is `true`, for further details see <<Binding>>. Measured in seconds. Default is `5`.
maxRequestHeaderSize::
The maximum allowed size for the HTTP request line and HTTP request headers.
Measured in bytes.
Default is `8192`.
maxResponseHeaderSize::
The maximum allowed size for the HTTP response headers.
Measured in bytes.
Default is `8192`.
=== Binding

View File

@ -56,6 +56,8 @@ import org.apache.activemq.artemis.core.server.ActiveMQComponent;
import org.apache.activemq.artemis.dto.BindingDTO;
import org.apache.activemq.artemis.dto.RequestLogDTO;
import org.apache.activemq.artemis.dto.WebServerDTO;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -143,6 +145,75 @@ public class WebServerComponentTest {
assertTrue(logEntryFound);
}
@Test
public void testLargeRequestHeader() throws Exception {
testLargeRequestHeader(false);
}
@Test
public void testLargeRequestHeaderNegative() throws Exception {
testLargeRequestHeader(true);
}
private void testLargeRequestHeader(boolean fail) throws Exception {
final int defaultRequestHeaderSize = new HttpConfiguration().getRequestHeaderSize();
BindingDTO bindingDTO = new BindingDTO();
bindingDTO.uri = "http://localhost:0";
WebServerDTO webServerDTO = new WebServerDTO();
webServerDTO.setBindings(Collections.singletonList(bindingDTO));
webServerDTO.path = "webapps";
webServerDTO.webContentEnabled = true;
if (!fail) {
webServerDTO.maxRequestHeaderSize = defaultRequestHeaderSize * 2;
}
WebServerComponent webServerComponent = new WebServerComponent();
assertFalse(webServerComponent.isStarted());
webServerComponent.configure(webServerDTO, "./src/test/resources/", "./src/test/resources/");
testedComponents.add(webServerComponent);
webServerComponent.start();
final int port = webServerComponent.getPort();
// Make the connection attempt.
CountDownLatch latch = new CountDownLatch(1);
final ClientHandler clientHandler = new ClientHandler(latch);
Channel ch = getChannel(port, clientHandler);
URI uri = new URI(URL);
// Prepare the HTTP request.
HttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri.getRawPath());
request.headers().set(HttpHeaderNames.HOST, "localhost");
StringBuilder foo = new StringBuilder();
for (int i = 0; i < defaultRequestHeaderSize + 1; i++) {
foo.append("a");
}
request.headers().set("foo", foo.toString());
// Send the HTTP request.
ch.writeAndFlush(request);
assertTrue(latch.await(5, TimeUnit.SECONDS));
assertEquals(fail, clientHandler.body.toString().contains("431"));
}
/* It's not clear how to create a functional test for the response header size so this test simply ensures the
* configuration is passed through as expected.
*/
@Test
public void testLargeResponseHeaderConfiguration() throws Exception {
BindingDTO bindingDTO = new BindingDTO();
bindingDTO.uri = "http://localhost:0";
WebServerDTO webServerDTO = new WebServerDTO();
webServerDTO.setBindings(Collections.singletonList(bindingDTO));
webServerDTO.path = "webapps";
webServerDTO.webContentEnabled = true;
webServerDTO.maxResponseHeaderSize = 123;
WebServerComponent webServerComponent = new WebServerComponent();
assertFalse(webServerComponent.isStarted());
webServerComponent.configure(webServerDTO, "./src/test/resources/", "./src/test/resources/");
testedComponents.add(webServerComponent);
webServerComponent.start();
assertEquals(123, ((HttpConnectionFactory)webServerComponent.getWebServer().getConnectors()[0].getConnectionFactories().iterator().next()).getHttpConfiguration().getResponseHeaderSize());
}
private Channel getChannel(int port, ClientHandler clientHandler) throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();