From e48988064d9243bc03a4fe81c086d35324241bfc Mon Sep 17 00:00:00 2001 From: Maxim Nesen Date: Tue, 21 May 2024 13:45:54 +0200 Subject: [PATCH 1/2] MAX_HEADER_SIZE for the Netty connector Signed-off-by: Maxim Nesen --- connectors/netty-connector/pom.xml | 6 + .../connector/NettyClientProperties.java | 54 +++++++- .../netty/connector/NettyConnector.java | 12 +- .../netty/connector/HugeHeaderTest.java | 122 ++++++++++++++++++ .../org/glassfish/jersey/test/JerseyTest.java | 3 +- .../jersey/test/spi/TestContainer.java | 11 +- .../test/jetty/JettyTestContainerFactory.java | 43 +++++- .../jetty/JettyTestContainerProperties.java | 36 ++++++ 8 files changed, 279 insertions(+), 8 deletions(-) create mode 100644 connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/HugeHeaderTest.java create mode 100644 test-framework/providers/jetty/src/main/java/org/glassfish/jersey/test/jetty/JettyTestContainerProperties.java diff --git a/connectors/netty-connector/pom.xml b/connectors/netty-connector/pom.xml index 088c6ec9bb..b547a990b2 100644 --- a/connectors/netty-connector/pom.xml +++ b/connectors/netty-connector/pom.xml @@ -49,6 +49,12 @@ ${project.version} test + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-jetty + ${project.version} + test + diff --git a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyClientProperties.java b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyClientProperties.java index 9c79d1281d..562edadd8b 100644 --- a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyClientProperties.java +++ b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyClientProperties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -129,4 +129,56 @@ public class NettyClientProperties { public static final Integer DEFAULT_EXPECT_100_CONTINUE_TIMEOUT_VALUE = 500; + + /** + * Parameter which allows extending of the header size for the Netty connector + * + * @since 2.44 + */ + public static final String + MAX_HEADER_SIZE = "jersey.config.client.netty.maxHeaderSize"; + + /** + * Default header size for Netty Connector. + * Taken from {@link io.netty.handler.codec.http.HttpClientCodec#HttpClientCodec(int, int, int)} + * + * @since 2.44 + */ + public static final Integer + DEFAULT_HEADER_SIZE = 8192; + + /** + * Parameter which allows extending of the initial line length for the Netty connector + * + * @since 2.44 + */ + public static final String + MAX_INITIAL_LINE_LENGTH = "jersey.config.client.netty.maxInitialLineLength"; + + /** + * Default initial line length for Netty Connector. + * Taken from {@link io.netty.handler.codec.http.HttpClientCodec#HttpClientCodec(int, int, int)} + * + * @since 2.44 + */ + public static final Integer + DEFAULT_INITIAL_LINE_LENGTH = 4096; + + /** + * Parameter which allows extending of the chunk size for the Netty connector + * + * @since 2.44 + */ + public static final String + MAX_CHUNK_SIZE = "jersey.config.client.netty.maxChunkSize"; + + /** + * Default chunk size for Netty Connector. + * Taken from {@link io.netty.handler.codec.http.HttpClientCodec#HttpClientCodec(int, int, int)} + * + * @since 2.44 + */ + public static final Integer + DEFAULT_CHUNK_SIZE = 8192; + } diff --git a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java index 1a548dcb26..faba326475 100644 --- a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java +++ b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java @@ -302,7 +302,17 @@ protected void initChannel(SocketChannel ch) throws Exception { p.addLast(sslHandler); } - p.addLast(new HttpClientCodec()); + final Integer maxHeaderSize = ClientProperties.getValue(config.getProperties(), + NettyClientProperties.MAX_HEADER_SIZE, + NettyClientProperties.DEFAULT_HEADER_SIZE); + final Integer maxChunkSize = ClientProperties.getValue(config.getProperties(), + NettyClientProperties.MAX_CHUNK_SIZE, + NettyClientProperties.DEFAULT_CHUNK_SIZE); + final Integer maxInitialLineLength = ClientProperties.getValue(config.getProperties(), + NettyClientProperties.MAX_INITIAL_LINE_LENGTH, + NettyClientProperties.DEFAULT_INITIAL_LINE_LENGTH); + + p.addLast(new HttpClientCodec(maxInitialLineLength, maxHeaderSize, maxChunkSize)); p.addLast(new ChunkedWriteHandler()); p.addLast(new HttpContentDecompressor()); } diff --git a/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/HugeHeaderTest.java b/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/HugeHeaderTest.java new file mode 100644 index 0000000000..6f2d433a8a --- /dev/null +++ b/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/HugeHeaderTest.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.netty.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.glassfish.jersey.test.jetty.JettyTestContainerFactory; +import org.glassfish.jersey.test.jetty.JettyTestContainerProperties; +import org.glassfish.jersey.test.spi.TestContainerException; +import org.glassfish.jersey.test.spi.TestContainerFactory; +import org.junit.jupiter.api.Test; + +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.Response; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class HugeHeaderTest extends JerseyTest { + + private static final int SERVER_HEADER_SIZE = 1234567; + + private static final String hugeHeader = + "abcdefghijklmnopqrstuvwxyz" + + "abcdefghijklmnopqrstuvwxyz" + + "abcdefghijklmnopqrstuvwxyz" + + "abcdefghijklmnopqrstuvwxyz" + + "abcdefghijklmnopqrstuvwxyz" + + "abcdefghijklmnopqrstuvwxyz" + + "abcdefghijklmnopqrstuvwxyz" + + "abcdefghijklmnopqrstuvwxyz" + + "abcdefghijklmnopqrstuvwxyz" + + "abcdefghijklmnopqrstuvwxyz"; + + @Path("/test") + public static class HttpMethodResource { + @POST + public Response post( + @HeaderParam("X-HUGE-HEADER") String hugeHeader, + String entity) { + + return Response.noContent() + .header("X-HUGE-HEADER", hugeHeader) + .header("X-HUGE-HEADER-SIZE", hugeHeader.length()) + .build(); + } + } + + @Override + protected Application configure() { + return new ResourceConfig(HugeHeaderTest.HttpMethodResource.class); + } + + @Override + protected TestContainerFactory getTestContainerFactory() throws TestContainerException { + final Map configurationProperties = new HashMap<>(); + configurationProperties.put(JettyTestContainerProperties.HEADER_SIZE, SERVER_HEADER_SIZE); + return new JettyTestContainerFactory(configurationProperties); + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new NettyConnectorProvider()); + } + + @Test + public void testContentHeaderTrunked() { + final StringBuffer veryHugeHeader = new StringBuffer(); + for (int i = 1; i < 33; i++) { + veryHugeHeader.append(hugeHeader); + } + final Response response = target("test").request() + .header("X-HUGE-HEADER", veryHugeHeader.toString()) + .post(null); + + assertNull(response.getHeaderString("X-HUGE-HEADER-SIZE")); + assertNull(response.getHeaderString("X-HUGE-HEADER")); + response.close(); + } + + @Test + public void testConnectorHeaderSize() { + final StringBuffer veryHugeHeader = new StringBuffer(); + for (int i = 1; i < 35; i++) { + veryHugeHeader.append(hugeHeader); + } + int headerSize = veryHugeHeader.length(); + Response response = target("test") + .property(NettyClientProperties.MAX_HEADER_SIZE, 77750) + .request() + + + .header("X-HUGE-HEADER", veryHugeHeader.toString()) + .post(null); + assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus()); + + assertEquals(String.valueOf(headerSize), response.getHeaderString("X-HUGE-HEADER-SIZE")); + assertEquals(veryHugeHeader.toString(), response.getHeaderString("X-HUGE-HEADER")); + response.close(); + } +} diff --git a/test-framework/core/src/main/java/org/glassfish/jersey/test/JerseyTest.java b/test-framework/core/src/main/java/org/glassfish/jersey/test/JerseyTest.java index b1d8268713..33de7d989b 100644 --- a/test-framework/core/src/main/java/org/glassfish/jersey/test/JerseyTest.java +++ b/test-framework/core/src/main/java/org/glassfish/jersey/test/JerseyTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -621,6 +621,7 @@ public void setUp() throws Exception { if (!isConcurrent() || activeThreadCount.getAndIncrement() == 0) { registerLogHandlerIfEnabled(); final TestContainer testContainer = createTestContainer(context); + testContainer.configureContainer(); // Set current instance of test container and start it. setTestContainer(testContainer); diff --git a/test-framework/core/src/main/java/org/glassfish/jersey/test/spi/TestContainer.java b/test-framework/core/src/main/java/org/glassfish/jersey/test/spi/TestContainer.java index 68a5bf8df0..d2a0030f4d 100644 --- a/test-framework/core/src/main/java/org/glassfish/jersey/test/spi/TestContainer.java +++ b/test-framework/core/src/main/java/org/glassfish/jersey/test/spi/TestContainer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -54,4 +54,13 @@ public interface TestContainer { * Stop the container. */ public void stop(); + + /** + * optional method to configure container before it's being started + * + * @since 2.44 + */ + default void configureContainer() { + + } } diff --git a/test-framework/providers/jetty/src/main/java/org/glassfish/jersey/test/jetty/JettyTestContainerFactory.java b/test-framework/providers/jetty/src/main/java/org/glassfish/jersey/test/jetty/JettyTestContainerFactory.java index 6ea1f25527..254eb75ad4 100644 --- a/test-framework/providers/jetty/src/main/java/org/glassfish/jersey/test/jetty/JettyTestContainerFactory.java +++ b/test-framework/providers/jetty/src/main/java/org/glassfish/jersey/test/jetty/JettyTestContainerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -17,11 +17,13 @@ package org.glassfish.jersey.test.jetty; import java.net.URI; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.ws.rs.core.UriBuilder; +import org.eclipse.jetty.server.HttpConnectionFactory; import org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.jetty.JettyHttpContainerFactory; import org.glassfish.jersey.test.DeploymentContext; @@ -42,16 +44,21 @@ */ public class JettyTestContainerFactory implements TestContainerFactory { + private final Map propertiesMap; + private static class JettyTestContainer implements TestContainer { private static final Logger LOGGER = Logger.getLogger(JettyTestContainer.class.getName()); + private final Map propertiesMap; + private URI baseUri; private final Server server; - - private JettyTestContainer(final URI baseUri, final DeploymentContext context) { + private JettyTestContainer(final URI baseUri, final DeploymentContext context, final Map propertiesMap) { final URI base = UriBuilder.fromUri(baseUri).path(context.getContextPath()).build(); + this.propertiesMap = propertiesMap; + if (!"/".equals(base.getRawPath())) { throw new TestContainerException(String.format( "Cannot deploy on %s. Jetty HTTP container only supports deployment on root path.", @@ -68,6 +75,26 @@ private JettyTestContainer(final URI baseUri, final DeploymentContext context) { this.server = JettyHttpContainerFactory.createServer(this.baseUri, context.getResourceConfig(), false); } + @Override + public void configureContainer() { + + if (propertiesMap == null + || !propertiesMap.containsKey(JettyTestContainerProperties.HEADER_SIZE)) { + return; + } + + for (Connector c : server.getConnectors()) { + c.getConnectionFactory(HttpConnectionFactory.class) + .getHttpConfiguration().setRequestHeaderSize( + (Integer) propertiesMap.get(JettyTestContainerProperties.HEADER_SIZE)); + c.getConnectionFactory(HttpConnectionFactory.class) + .getHttpConfiguration().setResponseHeaderSize( + (Integer) propertiesMap.get(JettyTestContainerProperties.HEADER_SIZE)); + c.getConnectionFactory(HttpConnectionFactory.class); + } + + } + @Override public ClientConfig getClientConfig() { return null; @@ -123,6 +150,14 @@ public void stop() { @Override public TestContainer create(final URI baseUri, final DeploymentContext context) throws IllegalArgumentException { - return new JettyTestContainer(baseUri, context); + return new JettyTestContainer(baseUri, context, propertiesMap); + } + + public JettyTestContainerFactory() { + this(null); + } + + public JettyTestContainerFactory(Map properties) { + this.propertiesMap = properties; } } diff --git a/test-framework/providers/jetty/src/main/java/org/glassfish/jersey/test/jetty/JettyTestContainerProperties.java b/test-framework/providers/jetty/src/main/java/org/glassfish/jersey/test/jetty/JettyTestContainerProperties.java new file mode 100644 index 0000000000..beabef2a8b --- /dev/null +++ b/test-framework/providers/jetty/src/main/java/org/glassfish/jersey/test/jetty/JettyTestContainerProperties.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.test.jetty; + +import org.glassfish.jersey.internal.util.PropertiesClass; + +/** + * Properties which relates only to Jetty test container configuration + * + * @since 2.44 + */ +@PropertiesClass +public class JettyTestContainerProperties { + + /** + * Parameter which allows settings custom header size for request and response. + * + * @since 2.44 + */ + public static final String HEADER_SIZE = "jersey.test.jetty.container.header.size"; + +} From e4c502d21cc4950c72039d746c9b1b86cbf42f4b Mon Sep 17 00:00:00 2001 From: Maxim Nesen Date: Wed, 22 May 2024 08:37:08 +0200 Subject: [PATCH 2/2] Netty Connector properties documentation Signed-off-by: Maxim Nesen --- docs/src/main/docbook/appendix-properties.xml | 36 +++++++++++++++++++ docs/src/main/docbook/jersey.ent | 3 ++ 2 files changed, 39 insertions(+) diff --git a/docs/src/main/docbook/appendix-properties.xml b/docs/src/main/docbook/appendix-properties.xml index 6f5036b779..a33bbad679 100644 --- a/docs/src/main/docbook/appendix-properties.xml +++ b/docs/src/main/docbook/appendix-properties.xml @@ -2171,6 +2171,42 @@ + + &jersey.netty.NettyClientProperties.MAX_HEADER_SIZE; + jersey.config.client.netty.maxHeaderSize + + + Parameter which allows extending of the header size for the Netty connector + + Default header size is 8192b. + Since 2.44 + + + + + &jersey.netty.NettyClientProperties.MAX_CHUNK_SIZE; + jersey.config.client.netty.maxChunkSize + + + Parameter which allows extending of the chunk size for the Netty connector + + Default chunk size is 8192b. + Since 2.44 + + + + + &jersey.netty.NettyClientProperties.MAX_INITIAL_LINE_LENGTH; + jersey.config.client.netty.maxInitialLineLength + + + Parameter which allows extending of the initial line length for the Netty connector + + Default initial line length is 4096b. + Since 2.44 + + + diff --git a/docs/src/main/docbook/jersey.ent b/docs/src/main/docbook/jersey.ent index ccbe0216ea..171bab9cf4 100644 --- a/docs/src/main/docbook/jersey.ent +++ b/docs/src/main/docbook/jersey.ent @@ -571,6 +571,9 @@ NettyClientProperties.MAX_REDIRECTS" > NettyClientProperties.PRESERVE_METHOD_ON_REDIRECT" > NettyClientProperties.EXPECT_100_CONTINUE_TIMEOUT" > +NettyClientProperties.MAX_HEADER_SIZE" > +NettyClientProperties.MAX_INITIAL_LINE_LENGTH" > +NettyClientProperties.MAX_CHUNK_SIZE" > NettyConnectorProvider"> ApplicationHandler"> @BackgroundScheduler">