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"; + +}