diff --git a/connectors/apache-connector/src/main/java/org/glassfish/jersey/apache/connector/ApacheConnector.java b/connectors/apache-connector/src/main/java/org/glassfish/jersey/apache/connector/ApacheConnector.java index 11f2091d58..d7557444cc 100644 --- a/connectors/apache-connector/src/main/java/org/glassfish/jersey/apache/connector/ApacheConnector.java +++ b/connectors/apache-connector/src/main/java/org/glassfish/jersey/apache/connector/ApacheConnector.java @@ -884,7 +884,7 @@ protected void prepareSocket(SSLSocket socket) throws IOException { if (objectRequest != null) { ClientRequest clientRequest = (ClientRequest) objectRequest; SSLParamConfigurator sniConfig = SSLParamConfigurator.builder().request(clientRequest) - .setSNIHostName(clientRequest.getConfiguration()).build(); + .setSNIHostName(clientRequest).build(); sniConfig.setSNIServerName(socket); } } diff --git a/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5Connector.java b/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5Connector.java index 4eecc17c2b..3b6bed5ad5 100644 --- a/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5Connector.java +++ b/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5Connector.java @@ -883,7 +883,7 @@ protected void prepareSocket(SSLSocket socket) throws IOException { if (objectRequest != null) { ClientRequest clientRequest = (ClientRequest) objectRequest; SSLParamConfigurator sniConfig = SSLParamConfigurator.builder().request(clientRequest) - .setSNIHostName(clientRequest.getConfiguration()).build(); + .setSNIHostName(clientRequest).build(); sniConfig.setSNIServerName(socket); final int socketTimeout = ((ClientRequest) objectRequest).resolveProperty(ClientProperties.READ_TIMEOUT, -1); diff --git a/connectors/netty-connector/pom.xml b/connectors/netty-connector/pom.xml index ff21c9bc73..906358f9f5 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 + @@ -75,4 +81,26 @@ + + + JettyExclude + + 1.8 + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org/glassfish/jersey/netty/connector/HugeHeaderTest.java + + + + + + + + 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 e6aad835c9..4fbec4ccc0 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 @@ -34,7 +34,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -89,6 +88,7 @@ import org.glassfish.jersey.client.innate.http.SSLParamConfigurator; import org.glassfish.jersey.client.spi.AsyncConnectorCallback; import org.glassfish.jersey.client.spi.Connector; +import org.glassfish.jersey.innate.VirtualThreadUtil; import org.glassfish.jersey.message.internal.OutboundMessageContext; import org.glassfish.jersey.netty.connector.internal.NettyEntityWriter; @@ -130,14 +130,15 @@ class NettyConnector implements Connector { NettyConnector(Client client) { - final Map properties = client.getConfiguration().getProperties(); + final Configuration configuration = client.getConfiguration(); + final Map properties = configuration.getProperties(); final Object threadPoolSize = properties.get(ClientProperties.ASYNC_THREADPOOL_SIZE); if (threadPoolSize != null && threadPoolSize instanceof Integer && (Integer) threadPoolSize > 0) { - executorService = Executors.newFixedThreadPool((Integer) threadPoolSize); + executorService = VirtualThreadUtil.withConfig(configuration).newFixedThreadPool((Integer) threadPoolSize); this.group = new NioEventLoopGroup((Integer) threadPoolSize); } else { - executorService = Executors.newCachedThreadPool(); + executorService = VirtualThreadUtil.withConfig(configuration).newCachedThreadPool(); this.group = new NioEventLoopGroup(); } @@ -208,7 +209,7 @@ protected void execute(final ClientRequest jerseyRequest, final Set redirec try { final SSLParamConfigurator sslConfig = SSLParamConfigurator.builder() - .request(jerseyRequest).setSNIAlways(true).setSNIHostName(jerseyRequest.getConfiguration()).build(); + .request(jerseyRequest).setSNIAlways(true).setSNIHostName(jerseyRequest).build(); String key = requestUri.getScheme() + "://" + sslConfig.getSNIHostName() + ":" + port; ArrayList conns; @@ -303,7 +304,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..dddf8d95c0 --- /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 jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Application; +import jakarta.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/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerFactory.java b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerFactory.java index 28d03d4962..11d74a607c 100644 --- a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerFactory.java +++ b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerFactory.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 @@ -18,10 +18,15 @@ import java.io.IOException; import java.net.URI; +import java.util.concurrent.ThreadFactory; import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.core.Configuration; import org.glassfish.jersey.grizzly2.httpserver.internal.LocalizationMessages; +import org.glassfish.jersey.innate.VirtualThreadSupport; +import org.glassfish.jersey.innate.VirtualThreadUtil; +import org.glassfish.jersey.innate.virtual.LoomishExecutors; import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder; import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler; import org.glassfish.jersey.server.ApplicationHandler; @@ -281,11 +286,20 @@ public static HttpServer createHttpServer(final URI uri, : uri.getPort(); final NetworkListener listener = new NetworkListener("grizzly", host, port); + final Configuration configuration = handler != null ? handler.getConfiguration().getConfiguration() : null; - listener.getTransport().getWorkerThreadPoolConfig().setThreadFactory(new ThreadFactoryBuilder() + final LoomishExecutors executors = VirtualThreadUtil.withConfig(configuration, false); + final ThreadFactory threadFactory = new ThreadFactoryBuilder() .setNameFormat("grizzly-http-server-%d") .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler()) - .build()); + .setThreadFactory(executors.getThreadFactory()) + .build(); + + if (executors.isVirtual()) { + listener.getTransport().setWorkerThreadPool(executors.newCachedThreadPool()); + } else { + listener.getTransport().getWorkerThreadPoolConfig().setThreadFactory(threadFactory); + } listener.setSecure(secure); if (sslEngineConfigurator != null) { diff --git a/containers/grizzly2-servlet/src/main/java/org/glassfish/jersey/grizzly2/servlet/GrizzlyWebContainerFactory.java b/containers/grizzly2-servlet/src/main/java/org/glassfish/jersey/grizzly2/servlet/GrizzlyWebContainerFactory.java index a70ee7fc68..d513d0a455 100644 --- a/containers/grizzly2-servlet/src/main/java/org/glassfish/jersey/grizzly2/servlet/GrizzlyWebContainerFactory.java +++ b/containers/grizzly2-servlet/src/main/java/org/glassfish/jersey/grizzly2/servlet/GrizzlyWebContainerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -23,6 +23,7 @@ import jakarta.servlet.Servlet; import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; +import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.servlet.ServletContainer; import org.glassfish.jersey.uri.UriComponent; @@ -251,11 +252,13 @@ private static HttpServer create(URI u, Class c, Servlet serv } } + ResourceConfig configuration = new ResourceConfig(); if (initParams != null) { registration.setInitParameters(initParams); + configuration.addProperties((Map) initParams); } - HttpServer server = GrizzlyHttpServerFactory.createHttpServer(u); + HttpServer server = GrizzlyHttpServerFactory.createHttpServer(u, configuration); context.deploy(server); return server; } diff --git a/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java b/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java index 26a4b79581..cb66f211bf 100644 --- a/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java +++ b/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2023 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 @@ -20,7 +20,10 @@ import java.util.concurrent.ThreadFactory; import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.core.Configuration; +import org.glassfish.jersey.innate.VirtualThreadUtil; +import org.glassfish.jersey.innate.virtual.LoomishExecutors; import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder; import org.glassfish.jersey.jetty.internal.LocalizationMessages; import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler; @@ -254,7 +257,8 @@ public static Server createServer(final URI uri, } final int port = (uri.getPort() == -1) ? defaultPort : uri.getPort(); - final Server server = new Server(new JettyConnectorThreadPool()); + final Configuration configuration = handler != null ? handler.getConfiguration() : null; + final Server server = new Server(new JettyConnectorThreadPool(configuration)); final HttpConfiguration config = new HttpConfiguration(); if (sslContextFactory != null) { config.setSecureScheme("https"); @@ -292,10 +296,20 @@ public static Server createServer(final URI uri, // // Keeping this for backwards compatibility for the time being private static final class JettyConnectorThreadPool extends QueuedThreadPool { - private final ThreadFactory threadFactory = new ThreadFactoryBuilder() - .setNameFormat("jetty-http-server-%d") - .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler()) - .build(); + private final ThreadFactory threadFactory; + + private JettyConnectorThreadPool(Configuration configuration) { + final LoomishExecutors executors = VirtualThreadUtil.withConfig(configuration, false); + if (executors.isVirtual()) { + super.setMaxThreads(Integer.MAX_VALUE - 1); + } + + this.threadFactory = new ThreadFactoryBuilder() + .setNameFormat("jetty-http-server-%d") + .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler()) + .setThreadFactory(executors.getThreadFactory()) + .build(); + } @Override public Thread newThread(Runnable runnable) { diff --git a/core-client/src/main/java/org/glassfish/jersey/client/ClientExecutorProvidersConfigurator.java b/core-client/src/main/java/org/glassfish/jersey/client/ClientExecutorProvidersConfigurator.java index df26b22587..ab5b9f54f6 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/ClientExecutorProvidersConfigurator.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/ClientExecutorProvidersConfigurator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -32,11 +32,12 @@ import org.glassfish.jersey.internal.util.collection.Value; import org.glassfish.jersey.internal.util.collection.Values; import org.glassfish.jersey.model.internal.ComponentBag; -import org.glassfish.jersey.model.internal.ManagedObjectsFinalizer; import org.glassfish.jersey.process.internal.AbstractExecutorProvidersConfigurator; import org.glassfish.jersey.spi.ExecutorServiceProvider; import org.glassfish.jersey.spi.ScheduledExecutorServiceProvider; +import jakarta.ws.rs.core.Configuration; + /** * Configurator which initializes and register {@link ExecutorServiceProvider} and * {@link ScheduledExecutorServiceProvider}. @@ -64,7 +65,8 @@ class ClientExecutorProvidersConfigurator extends AbstractExecutorProvidersConfi @Override public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) { - Map runtimeProperties = bootstrapBag.getConfiguration().getProperties(); + final Configuration configuration = bootstrapBag.getConfiguration(); + Map runtimeProperties = configuration.getProperties(); ExecutorServiceProvider defaultAsyncExecutorProvider; ScheduledExecutorServiceProvider defaultScheduledExecutorProvider; @@ -94,12 +96,12 @@ public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) { .named("ClientAsyncThreadPoolSize"); injectionManager.register(asyncThreadPoolSizeBinding); - defaultAsyncExecutorProvider = new DefaultClientAsyncExecutorProvider(asyncThreadPoolSize); + defaultAsyncExecutorProvider = new DefaultClientAsyncExecutorProvider(asyncThreadPoolSize, configuration); } else { if (MANAGED_EXECUTOR_SERVICE != null) { defaultAsyncExecutorProvider = new ClientExecutorServiceProvider(MANAGED_EXECUTOR_SERVICE); } else { - defaultAsyncExecutorProvider = new DefaultClientAsyncExecutorProvider(0); + defaultAsyncExecutorProvider = new DefaultClientAsyncExecutorProvider(0, configuration); } } } diff --git a/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java b/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java index c5a920c0b4..88a3d017d7 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java @@ -467,38 +467,45 @@ public final class ClientProperties { public static final String QUERY_PARAM_STYLE = "jersey.config.client.uri.query.param.style"; /** + * Sets the {@link org.glassfish.jersey.client.spi.ConnectorProvider} class. Overrides the value from META-INF/services. + * *

- * Most connectors support HOST header value to be used as an SNIHostName. However, the HOST header is restricted in JDK. - * {@code HttpUrlConnector} and {@code JavaNetHttpConnector} need - * to have an extra System Property set to allow HOST header. - * As an option to HOST header, this property allows the HOST name to be pre-set on a Client and does not need to - * be set on each request. + * The value MUST be an instance of {@code String}. *

*

- * The value MUST be an instance of {@link java.lang.String}. + * The property is recognized by {@link ClientBuilder}. *

*

* The name of the configuration property is {@value}. *

- * @since 3.1.2 + * @since 2.40 */ - public static final String SNI_HOST_NAME = "jersey.config.client.sniHostName"; + public static final String CONNECTOR_PROVIDER = "jersey.config.client.connector.provider"; /** - * Sets the {@link org.glassfish.jersey.client.spi.ConnectorProvider} class. Overrides the value from META-INF/services. - * *

- * The value MUST be an instance of {@code String}. + * Sets the {@code hostName} to be used for calculating the {@link javax.net.ssl.SNIHostName} during the HTTPS request. + * Takes precedence over the HTTP HOST header, if set. *

*

- * The property is recognized by {@link ClientBuilder}. + * By default, the {@code SNIHostName} is set when the HOST HTTP header differs from the HTTP request host. + * When the {@code hostName} matches the HTTPS request host, the {@code SNIHostName} is not set, + * and the HTTP HOST header is not used for setting the {@code SNIHostName}. This allows for Domain Fronting. + *

+ *

+ * Most connectors support HOST header value to be used as an SNIHostName. However, the HOST header is restricted in JDK. + * {@code HttpUrlConnector} and {@code JavaNetHttpConnector} need + * to have an extra System Property set to allow HOST header. + *

+ *

+ * The value MUST be an instance of {@link java.lang.String}. *

*

* The name of the configuration property is {@value}. *

- * @since 2.40 + * @since 3.1.2 */ - public static final String CONNECTOR_PROVIDER = "jersey.config.client.connector.provider"; + public static final String SNI_HOST_NAME = "jersey.config.client.snihostname"; /** *

The {@link javax.net.ssl.SSLContext} {@link java.util.function.Supplier} to be used to set ssl context in the current diff --git a/core-client/src/main/java/org/glassfish/jersey/client/DefaultClientAsyncExecutorProvider.java b/core-client/src/main/java/org/glassfish/jersey/client/DefaultClientAsyncExecutorProvider.java index 2f9fa71006..2a19b0d314 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/DefaultClientAsyncExecutorProvider.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/DefaultClientAsyncExecutorProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -20,6 +20,9 @@ import jakarta.inject.Inject; import jakarta.inject.Named; +import jakarta.ws.rs.core.Configuration; +import jakarta.ws.rs.core.Context; + import org.glassfish.jersey.client.internal.LocalizationMessages; import org.glassfish.jersey.internal.util.collection.LazyValue; @@ -46,8 +49,9 @@ class DefaultClientAsyncExecutorProvider extends ThreadPoolExecutorProvider { * See also {@link org.glassfish.jersey.client.ClientProperties#ASYNC_THREADPOOL_SIZE}. */ @Inject - public DefaultClientAsyncExecutorProvider(@Named("ClientAsyncThreadPoolSize") final int poolSize) { - super("jersey-client-async-executor"); + public DefaultClientAsyncExecutorProvider(@Named("ClientAsyncThreadPoolSize") final int poolSize, + @Context Configuration configuration) { + super("jersey-client-async-executor", configuration); this.asyncThreadPoolSize = Values.lazy(new Value() { @Override diff --git a/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SSLParamConfigurator.java b/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SSLParamConfigurator.java index a92adf5472..79dc60e34b 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SSLParamConfigurator.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SSLParamConfigurator.java @@ -18,24 +18,21 @@ import org.glassfish.jersey.client.ClientProperties; import org.glassfish.jersey.client.ClientRequest; +import org.glassfish.jersey.http.HttpHeaders; import org.glassfish.jersey.internal.PropertiesResolver; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSocket; import jakarta.ws.rs.core.Configuration; -import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.UriBuilder; -import org.glassfish.jersey.internal.PropertiesResolver; -import org.glassfish.jersey.internal.util.PropertiesHelper; - import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; -import java.util.Properties; /** * A unified routines to configure {@link SSLParameters}. @@ -49,11 +46,11 @@ public final class SSLParamConfigurator { * Builder of the {@link SSLParamConfigurator} instance. */ public static final class Builder { - private URI uri = null; + private URI uri; private String sniHostNameHeader = null; - private String sniHostNameProperty = null; - private boolean setAlways = false; private String sniHostPrecedence = null; + private boolean setAlways = false; + /** * Sets the SNIHostName and {@link URI} from the {@link ClientRequest} instance. @@ -62,7 +59,7 @@ public static final class Builder { */ public Builder request(ClientRequest clientRequest) { this.sniHostNameHeader = getSniHostNameHeader(clientRequest.getHeaders()); - this.sniHostNameProperty = clientRequest.resolveProperty(ClientProperties.SNI_HOST_NAME, String.class); + this.sniHostPrecedence = resolveSniHostNameProperty(clientRequest); this.uri = clientRequest.getUri(); return this; } @@ -73,7 +70,7 @@ public Builder request(ClientRequest clientRequest) { * @return the builder instance */ public Builder configuration(Configuration configuration) { - this.sniHostNameProperty = (String) configuration.getProperty(ClientProperties.SNI_HOST_NAME); + this.sniHostPrecedence = getSniHostNameProperty(configuration); return this; } @@ -122,7 +119,7 @@ public Builder setSNIAlways(boolean setAlways) { * @return the builder instance. */ public Builder setSNIHostName(String hostName) { - sniHostPrecedence = trimSniHostName(hostName); + sniHostPrecedence = hostName; return this; } @@ -142,7 +139,7 @@ public Builder setSNIHostName(String hostName) { * @return the builder instance. */ public Builder setSNIHostName(Configuration configuration) { - return setSNIHostName((String) configuration.getProperty(ClientProperties.SNI_HOST_NAME)); + return setSNIHostName(getSniHostNameProperty(configuration)); } /** @@ -161,7 +158,7 @@ public Builder setSNIHostName(Configuration configuration) { * @return the builder instance. */ public Builder setSNIHostName(PropertiesResolver resolver) { - return setSNIHostName(resolver.resolveProperty(ClientProperties.SNI_HOST_NAME, String.class)); + return setSNIHostName(resolveSniHostNameProperty(resolver)); } /** @@ -179,29 +176,34 @@ private static String getSniHostNameHeader(Map> httpHeaders } final String hostHeader = hostHeaders.get(0).toString(); - return trimSniHostName(hostHeader); + return hostHeader; } - private static String trimSniHostName(String sniHostName) { - final String trimmedHeader; - if (sniHostName != null) { - int index = sniHostName.indexOf(':'); // RFC 7230 Host = uri-host [ ":" port ] ; - final String trimmedHeader0 = index != -1 ? sniHostName.substring(0, index).trim() : sniHostName.trim(); - trimmedHeader = trimmedHeader0.isEmpty() ? sniHostName : trimmedHeader0; - } else { - trimmedHeader = null; + private static String resolveSniHostNameProperty(PropertiesResolver resolver) { + String property = resolver.resolveProperty(ClientProperties.SNI_HOST_NAME, String.class); + if (property == null) { + property = resolver.resolveProperty(ClientProperties.SNI_HOST_NAME.toLowerCase(Locale.ROOT), String.class); } + return property; + } - return trimmedHeader; + private static String getSniHostNameProperty(Configuration configuration) { + Object property = configuration.getProperty(ClientProperties.SNI_HOST_NAME); + if (property == null) { + property = configuration.getProperty(ClientProperties.SNI_HOST_NAME.toLowerCase(Locale.ROOT)); + } + return (String) property; } } private SSLParamConfigurator(SSLParamConfigurator.Builder builder) { - String sniHostName = builder.sniHostNameHeader == null ? builder.sniHostNameProperty : builder.sniHostNameHeader; uri = builder.uri; - sniConfigurator = SniConfigurator.createWhenHostHeader(uri, - builder.sniHostPrecedence != null ? builder.sniHostPrecedence : sniHostName, - builder.setAlways); + if (builder.sniHostPrecedence == null) { + sniConfigurator = SniConfigurator.createWhenHostHeader(uri, builder.sniHostNameHeader, builder.setAlways); + } else { + // Do not set SNI always, the property can be used to turn the SNI off + sniConfigurator = SniConfigurator.createWhenHostHeader(uri, builder.sniHostPrecedence, false); + } } /** diff --git a/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SniConfigurator.java b/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SniConfigurator.java index d5d916db19..1ecdc59dd8 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SniConfigurator.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SniConfigurator.java @@ -50,27 +50,29 @@ String getHostName() { } /** - * Create {@link SniConfigurator} when {@link HttpHeaders#HOST} is set different from the request URI host + * Create {@link SniConfigurator} when {@code sniHost} is set different from the request URI host * (or {@code whenDiffer}.is false). * @param hostUri the Uri of the HTTP request - * @param sniHostName the SniHostName either from HttpHeaders or the - * {@link org.glassfish.jersey.client.ClientProperties#SNI_HOST_NAME} property from Configuration object. + * @param sniHost the preferred host name to create the {@link SNIHostName} * @param whenDiffer create {@SniConfigurator only when different from the request URI host} - * @return Optional {@link SniConfigurator} or empty when {@link HttpHeaders#HOST} is equal to the requestHost + * @return Optional {@link SniConfigurator} or empty when {@code sniHost} is equal to the requestHost */ - static Optional createWhenHostHeader(URI hostUri, String sniHostName, boolean whenDiffer) { - if (sniHostName == null) { + static Optional createWhenHostHeader(URI hostUri, String sniHost, boolean whenDiffer) { + final String trimmedHeader; + if (sniHost != null) { + int index = sniHost.indexOf(':'); // RFC 7230 Host = uri-host [ ":" port ] ; + final String trimmedHeader0 = index != -1 ? sniHost.substring(0, index).trim() : sniHost.trim(); + trimmedHeader = trimmedHeader0.isEmpty() ? sniHost : trimmedHeader0; + } else { return Optional.empty(); } - if (hostUri != null) { - final String hostUriString = hostUri.getHost(); - if (!whenDiffer && hostUriString.equals(sniHostName)) { - return Optional.empty(); - } + final String hostUriString = hostUri.getHost(); + if (!whenDiffer && hostUriString.equals(trimmedHeader)) { + return Optional.empty(); } - return Optional.of(new SniConfigurator(sniHostName)); + return Optional.of(new SniConfigurator(trimmedHeader)); } /** diff --git a/core-client/src/test/java/org/glassfish/jersey/client/innate/http/SSLParamConfiguratorTest.java b/core-client/src/test/java/org/glassfish/jersey/client/innate/http/SSLParamConfiguratorTest.java new file mode 100644 index 0000000000..06dcdec4e3 --- /dev/null +++ b/core-client/src/test/java/org/glassfish/jersey/client/innate/http/SSLParamConfiguratorTest.java @@ -0,0 +1,158 @@ +/* + * 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.client.innate.http; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.ClientRequest; +import org.glassfish.jersey.client.JerseyClient; +import org.glassfish.jersey.http.HttpHeaders; +import org.glassfish.jersey.internal.MapPropertiesDelegate; +import org.glassfish.jersey.internal.PropertiesDelegate; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.core.MultivaluedHashMap; + +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class SSLParamConfiguratorTest { + @Test + public void testNoHost() { + final URI uri = URI.create("http://xxx.com:8080"); + final JerseyClient client = (JerseyClient) ClientBuilder.newClient(); + final ClientConfig config = client.getConfiguration(); + final PropertiesDelegate delegate = new MapPropertiesDelegate(); + ClientRequest request = new ClientRequest(uri, config, delegate) {}; + SSLParamConfigurator configurator = SSLParamConfigurator.builder().request(request).build(); + MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(false)); + } + + @Test + public void testHostHeaderHasPrecedence() { + final URI uri = URI.create("http://xxx.com:8080"); + final JerseyClient client = (JerseyClient) ClientBuilder.newClient(); + final ClientConfig config = client.getConfiguration(); + final PropertiesDelegate delegate = new MapPropertiesDelegate(); + ClientRequest request = new ClientRequest(uri, config, delegate) {}; + request.getHeaders().add(HttpHeaders.HOST, "yyy.com"); + SSLParamConfigurator configurator = SSLParamConfigurator.builder().request(request).build(); + MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true)); + MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("yyy.com")); + } + + @Test + public void testPropertyOnClientHasPrecedence() { + final URI uri = URI.create("http://xxx.com:8080"); + final JerseyClient client = (JerseyClient) ClientBuilder.newClient(); + final ClientConfig config = client.getConfiguration(); + final PropertiesDelegate delegate = new MapPropertiesDelegate(); + client.property(ClientProperties.SNI_HOST_NAME, "yyy.com"); + ClientRequest request = new ClientRequest(uri, config, delegate) {}; + SSLParamConfigurator configurator = SSLParamConfigurator.builder().request(request).build(); + MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true)); + MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("yyy.com")); + } + + @Test + public void testPropertyOnDelegateHasPrecedence() { + final URI uri = URI.create("http://xxx.com:8080"); + final JerseyClient client = (JerseyClient) ClientBuilder.newClient(); + final ClientConfig config = client.getConfiguration(); + final PropertiesDelegate delegate = new MapPropertiesDelegate(); + client.property(ClientProperties.SNI_HOST_NAME, "yyy.com"); + delegate.setProperty(ClientProperties.SNI_HOST_NAME, "zzz.com"); + ClientRequest request = new ClientRequest(uri, config, delegate) {}; + SSLParamConfigurator configurator = SSLParamConfigurator.builder().request(request).build(); + MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true)); + MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("zzz.com")); + } + + @Test + public void testPropertyOnDelegateHasPrecedenceOverHost() { + final URI uri = URI.create("http://xxx.com:8080"); + final JerseyClient client = (JerseyClient) ClientBuilder.newClient(); + final ClientConfig config = client.getConfiguration(); + final PropertiesDelegate delegate = new MapPropertiesDelegate(); + client.property(ClientProperties.SNI_HOST_NAME, "yyy.com"); + delegate.setProperty(ClientProperties.SNI_HOST_NAME, "zzz.com"); + ClientRequest request = new ClientRequest(uri, config, delegate) {}; + request.getHeaders().add(HttpHeaders.HOST, "www.com"); + SSLParamConfigurator configurator = SSLParamConfigurator.builder().request(request).build(); + MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true)); + MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("zzz.com")); + } + + @Test + public void testDisableSni() { + final URI uri = URI.create("http://xxx.com:8080"); + final JerseyClient client = (JerseyClient) ClientBuilder.newClient(); + final ClientConfig config = client.getConfiguration(); + final PropertiesDelegate delegate = new MapPropertiesDelegate(); + client.property(ClientProperties.SNI_HOST_NAME, "yyy.com"); + delegate.setProperty(ClientProperties.SNI_HOST_NAME, "xxx.com"); + ClientRequest request = new ClientRequest(uri, config, delegate) {}; + request.getHeaders().add(HttpHeaders.HOST, "www.com"); + SSLParamConfigurator configurator = SSLParamConfigurator.builder().request(request).build(); + MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(false)); + MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("xxx.com")); + } + + @Test + public void testLowerCasePropertyOnClientHasPrecedence() { + final URI uri = URI.create("http://xxx.com:8080"); + final JerseyClient client = (JerseyClient) ClientBuilder.newClient(); + final ClientConfig config = client.getConfiguration(); + final PropertiesDelegate delegate = new MapPropertiesDelegate(); + client.property(ClientProperties.SNI_HOST_NAME.toLowerCase(Locale.ROOT), "yyy.com"); + ClientRequest request = new ClientRequest(uri, config, delegate) {}; + request.getHeaders().add(HttpHeaders.HOST, "www.com"); + SSLParamConfigurator configurator = SSLParamConfigurator.builder().request(request).build(); + MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true)); + MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("yyy.com")); + } + + @Test + public void testUriAndHeadersAndConfig() { + final URI uri = URI.create("http://xxx.com:8080"); + final JerseyClient client = (JerseyClient) ClientBuilder.newClient(); + Map> httpHeaders = new MultivaluedHashMap<>(); + httpHeaders.put(HttpHeaders.HOST, Collections.singletonList("www.com")); + SSLParamConfigurator configurator = SSLParamConfigurator.builder() + .uri(uri) + .headers(httpHeaders) + .configuration(client.getConfiguration()) + .build(); + MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true)); + MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("www.com")); + + client.property(ClientProperties.SNI_HOST_NAME, "yyy.com"); + configurator = SSLParamConfigurator.builder() + .uri(uri) + .headers(httpHeaders) + .configuration(client.getConfiguration()) + .build(); + MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true)); + MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("yyy.com")); + } +} diff --git a/core-common/pom.xml b/core-common/pom.xml index 13ab2a5b8a..02713f1cc0 100644 --- a/core-common/pom.xml +++ b/core-common/pom.xml @@ -340,7 +340,7 @@ - diff --git a/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java b/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java index e71e5e14fc..d7f59859e6 100644 --- a/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java +++ b/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2023 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 @@ -320,6 +320,30 @@ public final class CommonProperties { */ public static final String PARAM_CONVERTERS_THROW_IAE = "jersey.config.paramconverters.throw.iae"; + /** + *

+ * Defines the {@link java.util.concurrent.ThreadFactory} to be used by internal default Executor Services. + *

+ *

+ * The default is {@link java.util.concurrent.Executors#defaultThreadFactory()} on platform threads and + * {@code Thread.ofVirtual().factory()} on virtual threads. + *

+ * @since 2.44 + */ + public static String THREAD_FACTORY = "jersey.config.threads.factory"; + + /** + *

+ * Defines whether the virtual threads should be used by Jersey on JDK 21+ when not using an exact number + * of threads by {@code FixedThreadPool}. + *

+ *

+ * The default is {@code false} for this version of Jersey, and {@code true} for Jersey 3.1+. + *

+ * @since 2.44 + */ + public static String USE_VIRTUAL_THREADS = "jersey.config.threads.use.virtual"; + /** * Prevent instantiation. */ diff --git a/core-common/src/main/java/org/glassfish/jersey/innate/VirtualThreadUtil.java b/core-common/src/main/java/org/glassfish/jersey/innate/VirtualThreadUtil.java new file mode 100644 index 0000000000..3511d8e8e8 --- /dev/null +++ b/core-common/src/main/java/org/glassfish/jersey/innate/VirtualThreadUtil.java @@ -0,0 +1,89 @@ +/* + * 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.innate; + +import org.glassfish.jersey.CommonProperties; +import org.glassfish.jersey.innate.virtual.LoomishExecutors; + +import jakarta.ws.rs.core.Configuration; +import java.util.concurrent.ThreadFactory; + +/** + * Factory class to provide JDK specific implementation of bits related to the virtual thread support. + */ +public final class VirtualThreadUtil { + + private static final boolean USE_VIRTUAL_THREADS_BY_DEFAULT = false; + + /** + * Do not instantiate. + */ + private VirtualThreadUtil() { + throw new IllegalStateException(); + } + + /** + * Return an instance of {@link LoomishExecutors} based on a configuration property. + * @param config the {@link Configuration} + * @return the {@link LoomishExecutors} instance. + */ + public static LoomishExecutors withConfig(Configuration config) { + return withConfig(config, USE_VIRTUAL_THREADS_BY_DEFAULT); + } + + /** + * Return an instance of {@link LoomishExecutors} based on a configuration property. + * @param config the {@link Configuration} + * @param useVirtualByDefault the default use if not said otherwise by property + * @return the {@link LoomishExecutors} instance. + */ + public static LoomishExecutors withConfig(Configuration config, boolean useVirtualByDefault) { + ThreadFactory tfThreadFactory = null; + boolean useVirtualThreads = useVirtualThreads(config, useVirtualByDefault); + + if (config != null) { + Object threadFactory = config.getProperty(CommonProperties.THREAD_FACTORY); + if (threadFactory != null && ThreadFactory.class.isInstance(threadFactory)) { + tfThreadFactory = (ThreadFactory) threadFactory; + } + } + + return tfThreadFactory == null + ? VirtualThreadSupport.allowVirtual(useVirtualThreads) + : VirtualThreadSupport.allowVirtual(useVirtualThreads, tfThreadFactory); + } + + /** + * Check configuration if the use of the virtual threads is expected or return the default value if not. + * @param config the {@link Configuration} + * @param useByDefault the default expectation + * @return the expected + */ + private static boolean useVirtualThreads(Configuration config, boolean useByDefault) { + boolean bUseVirtualThreads = useByDefault; + if (config != null) { + Object useVirtualThread = config.getProperty(CommonProperties.USE_VIRTUAL_THREADS); + if (useVirtualThread != null && Boolean.class.isInstance(useVirtualThread)) { + bUseVirtualThreads = (boolean) useVirtualThread; + } + if (useVirtualThread != null && String.class.isInstance(useVirtualThread)) { + bUseVirtualThreads = Boolean.parseBoolean(useVirtualThread.toString()); + } + } + return bUseVirtualThreads; + } +} diff --git a/core-common/src/main/java/org/glassfish/jersey/innate/virtual/LoomishExecutors.java b/core-common/src/main/java/org/glassfish/jersey/innate/virtual/LoomishExecutors.java new file mode 100644 index 0000000000..906574623c --- /dev/null +++ b/core-common/src/main/java/org/glassfish/jersey/innate/virtual/LoomishExecutors.java @@ -0,0 +1,53 @@ +/* + * 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.innate.virtual; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +/** + * {@link Executors} facade to support virtual threads. + */ +public interface LoomishExecutors { + /** + * Creates a thread pool that creates new threads as needed and uses virtual threads if available. + * @return the newly created thread pool + */ + ExecutorService newCachedThreadPool(); + + /** + * Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue + * and uses virtual threads if available + * @param nThreads – the number of threads in the pool + * @return the newly created thread pool + */ + ExecutorService newFixedThreadPool(int nThreads); + + /** + * Returns thread factory used to create new threads + * @return thread factory used to create new threads + * @see Executors#defaultThreadFactory() + */ + ThreadFactory getThreadFactory(); + + /** + * Return true if the virtual thread use is requested. + * @return whether the virtual thread use is requested. + */ + boolean isVirtual(); +} diff --git a/core-common/src/main/java/org/glassfish/jersey/innate/virtual/package-info.java b/core-common/src/main/java/org/glassfish/jersey/innate/virtual/package-info.java new file mode 100644 index 0000000000..c56f91ba04 --- /dev/null +++ b/core-common/src/main/java/org/glassfish/jersey/innate/virtual/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 + */ + +/** + * Jersey innate packages. The innate packages will not be opened by JPMS outside of Jersey. + * Not for public use. + * This virtual package should contain only classes that do not have dependencies on Jersey, or the REST API to be buildable with + * ant for multi-release. + */ +package org.glassfish.jersey.innate.virtual; diff --git a/core-common/src/main/java/org/glassfish/jersey/spi/AbstractThreadPoolProvider.java b/core-common/src/main/java/org/glassfish/jersey/spi/AbstractThreadPoolProvider.java index 27cf44959c..79ac262a2f 100644 --- a/core-common/src/main/java/org/glassfish/jersey/spi/AbstractThreadPoolProvider.java +++ b/core-common/src/main/java/org/glassfish/jersey/spi/AbstractThreadPoolProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -29,6 +29,7 @@ import java.util.logging.Level; import java.util.logging.Logger; +import org.glassfish.jersey.innate.VirtualThreadUtil; import org.glassfish.jersey.internal.LocalizationMessages; import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder; import org.glassfish.jersey.internal.util.ExtendedLogger; @@ -37,6 +38,8 @@ import org.glassfish.jersey.internal.util.collection.Values; import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler; +import jakarta.ws.rs.core.Configuration; + /** * Abstract thread pool executor provider. *

@@ -69,9 +72,7 @@ public abstract class AbstractThreadPoolProvider i private final String name; private final AtomicBoolean closed = new AtomicBoolean(false); - private final LazyValue lazyExecutorServiceProvider = - Values.lazy((Value) () -> createExecutor(getCorePoolSize(), createThreadFactory(), getRejectedExecutionHandler())); - + private final LazyValue lazyExecutorServiceProvider; /** * Inheritance constructor. * @@ -79,7 +80,20 @@ public abstract class AbstractThreadPoolProvider i * provided thread pool executor. */ protected AbstractThreadPoolProvider(final String name) { + this(name, null); + } + + /** + * Inheritance constructor. + * + * @param name name of the provided thread pool executor. Will be used in the names of threads created & used by the + * provided thread pool executor. + * @param configuration {@link Configuration} properties. + */ + protected AbstractThreadPoolProvider(final String name, Configuration configuration) { this.name = name; + lazyExecutorServiceProvider = Values.lazy((Value) () -> + createExecutor(getCorePoolSize(), createThreadFactory(configuration), getRejectedExecutionHandler())); } /** @@ -208,9 +222,10 @@ protected ThreadFactory getBackingThreadFactory() { return null; } - private ThreadFactory createThreadFactory() { + private ThreadFactory createThreadFactory(Configuration configuration) { final ThreadFactoryBuilder factoryBuilder = new ThreadFactoryBuilder() .setNameFormat(name + "-%d") + .setThreadFactory(VirtualThreadUtil.withConfig(configuration).getThreadFactory()) .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler()); final ThreadFactory backingThreadFactory = getBackingThreadFactory(); diff --git a/core-common/src/main/java/org/glassfish/jersey/spi/ScheduledThreadPoolExecutorProvider.java b/core-common/src/main/java/org/glassfish/jersey/spi/ScheduledThreadPoolExecutorProvider.java index e805b237b2..348db2b1bc 100644 --- a/core-common/src/main/java/org/glassfish/jersey/spi/ScheduledThreadPoolExecutorProvider.java +++ b/core-common/src/main/java/org/glassfish/jersey/spi/ScheduledThreadPoolExecutorProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -23,6 +23,7 @@ import java.util.concurrent.ThreadFactory; import jakarta.annotation.PreDestroy; +import jakarta.ws.rs.core.Configuration; /** * Default implementation of the Jersey {@link org.glassfish.jersey.spi.ScheduledExecutorServiceProvider @@ -66,6 +67,17 @@ public ScheduledThreadPoolExecutorProvider(final String name) { super(name); } + /** + * Create a new instance of the scheduled thread pool executor provider. + * + * @param name provider name. The name will be used to name the threads created & used by the + * provisioned scheduled thread pool executor. + * @@param configuration {@link Configuration} properties. + */ + public ScheduledThreadPoolExecutorProvider(final String name, Configuration configuration) { + super(name, configuration); + } + @Override public ScheduledExecutorService getExecutorService() { return super.getExecutor(); diff --git a/core-common/src/main/java/org/glassfish/jersey/spi/ThreadPoolExecutorProvider.java b/core-common/src/main/java/org/glassfish/jersey/spi/ThreadPoolExecutorProvider.java index 09e0770e06..df3dd09974 100644 --- a/core-common/src/main/java/org/glassfish/jersey/spi/ThreadPoolExecutorProvider.java +++ b/core-common/src/main/java/org/glassfish/jersey/spi/ThreadPoolExecutorProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -26,6 +26,7 @@ import java.util.concurrent.TimeUnit; import jakarta.annotation.PreDestroy; +import jakarta.ws.rs.core.Configuration; /** * Default implementation of the Jersey {@link org.glassfish.jersey.spi.ExecutorServiceProvider executor service provider SPI}. @@ -61,6 +62,17 @@ public ThreadPoolExecutorProvider(final String name) { super(name); } + /** + * Create a new instance of the thread pool executor provider. + * + * @param name provider name. The name will be used to name the threads created & used by the + * provisioned thread pool executor. + * @param configuration {@link Configuration} properties. + */ + public ThreadPoolExecutorProvider(final String name, Configuration configuration) { + super(name, configuration); + } + @Override public ExecutorService getExecutorService() { return super.getExecutor(); diff --git a/core-common/src/main/java20-/org/glassfish/jersey/innate/VirtualThreadSupport.java b/core-common/src/main/java20-/org/glassfish/jersey/innate/VirtualThreadSupport.java index 90cafba6a2..867a65be87 100644 --- a/core-common/src/main/java20-/org/glassfish/jersey/innate/VirtualThreadSupport.java +++ b/core-common/src/main/java20-/org/glassfish/jersey/innate/VirtualThreadSupport.java @@ -16,11 +16,19 @@ package org.glassfish.jersey.innate; +import org.glassfish.jersey.innate.virtual.LoomishExecutors; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + /** * Utility class for the virtual thread support. */ public final class VirtualThreadSupport { + private static final LoomishExecutors NON_VIRTUAL = new NonLoomishExecutors(Executors.defaultThreadFactory()); + /** * Do not instantiate. */ @@ -35,4 +43,51 @@ private VirtualThreadSupport() { public static boolean isVirtualThread() { return false; } + + /** + * Return an instance of {@link LoomishExecutors} based on a permission to use virtual threads. + * @param allow whether to allow virtual threads. + * @return the {@link LoomishExecutors} instance. + */ + public static LoomishExecutors allowVirtual(boolean allow) { + return NON_VIRTUAL; + } + + /** + * Return an instance of {@link LoomishExecutors} based on a permission to use virtual threads. + * @param allow whether to allow virtual threads. + * @param threadFactory the thread factory to be used by a the {@link ExecutorService}. + * @return the {@link LoomishExecutors} instance. + */ + public static LoomishExecutors allowVirtual(boolean allow, ThreadFactory threadFactory) { + return new NonLoomishExecutors(threadFactory); + } + + private static final class NonLoomishExecutors implements LoomishExecutors { + private final ThreadFactory threadFactory; + + private NonLoomishExecutors(ThreadFactory threadFactory) { + this.threadFactory = threadFactory; + } + + @Override + public ExecutorService newCachedThreadPool() { + return Executors.newCachedThreadPool(); + } + + @Override + public ExecutorService newFixedThreadPool(int nThreads) { + return Executors.newFixedThreadPool(nThreads); + } + + @Override + public ThreadFactory getThreadFactory() { + return threadFactory; + } + + @Override + public boolean isVirtual() { + return false; + } + } } diff --git a/core-common/src/main/java21/org/glassfish/jersey/innate/VirtualThreadSupport.java b/core-common/src/main/java21/org/glassfish/jersey/innate/VirtualThreadSupport.java index 74f58ba9da..0e7d6959ea 100644 --- a/core-common/src/main/java21/org/glassfish/jersey/innate/VirtualThreadSupport.java +++ b/core-common/src/main/java21/org/glassfish/jersey/innate/VirtualThreadSupport.java @@ -16,11 +16,20 @@ package org.glassfish.jersey.innate; +import org.glassfish.jersey.innate.virtual.LoomishExecutors; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + /** * Utility class for the virtual thread support. */ public final class VirtualThreadSupport { + private static final LoomishExecutors VIRTUAL_THREADS = new Java21LoomishExecutors(Thread.ofVirtual().factory()); + private static final LoomishExecutors NON_VIRTUAL_THREADS = new NonLoomishExecutors(Executors.defaultThreadFactory()); + /** * Do not instantiate. */ @@ -35,4 +44,80 @@ private VirtualThreadSupport() { public static boolean isVirtualThread() { return Thread.currentThread().isVirtual(); } + + /** + * Return an instance of {@link LoomishExecutors} based on a permission to use virtual threads. + * @param allow whether to allow virtual threads. + * @return the {@link LoomishExecutors} instance. + */ + public static LoomishExecutors allowVirtual(boolean allow) { + return allow ? VIRTUAL_THREADS : NON_VIRTUAL_THREADS; + } + + /** + * Return an instance of {@link LoomishExecutors} based on a permission to use virtual threads. + * @param allow whether to allow virtual threads. + * @param threadFactory the thread factory to be used by a the {@link ExecutorService}. + * @return the {@link LoomishExecutors} instance. + */ + public static LoomishExecutors allowVirtual(boolean allow, ThreadFactory threadFactory) { + return allow ? new Java21LoomishExecutors(threadFactory) : new NonLoomishExecutors(threadFactory); + } + + private static class NonLoomishExecutors implements LoomishExecutors { + private final ThreadFactory threadFactory; + + private NonLoomishExecutors(ThreadFactory threadFactory) { + this.threadFactory = threadFactory; + } + + @Override + public ExecutorService newCachedThreadPool() { + return Executors.newCachedThreadPool(getThreadFactory()); + } + + @Override + public ExecutorService newFixedThreadPool(int nThreads) { + return Executors.newFixedThreadPool(nThreads, getThreadFactory()); + } + + @Override + public ThreadFactory getThreadFactory() { + return threadFactory; + } + + @Override + public boolean isVirtual() { + return false; + } + } + + private static class Java21LoomishExecutors implements LoomishExecutors { + private final ThreadFactory threadFactory; + + private Java21LoomishExecutors(ThreadFactory threadFactory) { + this.threadFactory = threadFactory; + } + + @Override + public ExecutorService newCachedThreadPool() { + return Executors.newThreadPerTaskExecutor(getThreadFactory()); + } + + @Override + public ExecutorService newFixedThreadPool(int nThreads) { + ThreadFactory threadFactory = this == VIRTUAL_THREADS ? Executors.defaultThreadFactory() : getThreadFactory(); + return Executors.newFixedThreadPool(nThreads, threadFactory); + } + + @Override + public ThreadFactory getThreadFactory() { + return threadFactory; + } + + @Override + public boolean isVirtual() { + return true; + } + } } diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ServerExecutorProvidersConfigurator.java b/core-server/src/main/java/org/glassfish/jersey/server/ServerExecutorProvidersConfigurator.java index 77974f1946..d6209586ee 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/ServerExecutorProvidersConfigurator.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/ServerExecutorProvidersConfigurator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -21,13 +21,14 @@ import org.glassfish.jersey.internal.inject.InjectionManager; import org.glassfish.jersey.internal.inject.InstanceBinding; import org.glassfish.jersey.model.internal.ComponentBag; -import org.glassfish.jersey.model.internal.ManagedObjectsFinalizer; import org.glassfish.jersey.process.internal.AbstractExecutorProvidersConfigurator; import org.glassfish.jersey.spi.ExecutorServiceProvider; import org.glassfish.jersey.spi.ScheduledExecutorServiceProvider; import org.glassfish.jersey.spi.ScheduledThreadPoolExecutorProvider; import org.glassfish.jersey.spi.ThreadPoolExecutorProvider; +import jakarta.ws.rs.core.Configuration; + /** * Configurator which initializes and register {@link org.glassfish.jersey.spi.ExecutorServiceProvider} and * {@link org.glassfish.jersey.spi.ScheduledExecutorServiceProvider}. @@ -43,7 +44,7 @@ public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) { ComponentBag componentBag = runtimeConfig.getComponentBag(); // TODO: Do we need to register DEFAULT Executor and ScheduledExecutor to InjectionManager? - ScheduledExecutorServiceProvider defaultScheduledExecutorProvider = new DefaultBackgroundSchedulerProvider(); + ScheduledExecutorServiceProvider defaultScheduledExecutorProvider = new DefaultBackgroundSchedulerProvider(runtimeConfig); InstanceBinding schedulerBinding = Bindings .service(defaultScheduledExecutorProvider) .to(ScheduledExecutorServiceProvider.class) @@ -67,8 +68,8 @@ public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) { @BackgroundScheduler private static class DefaultBackgroundSchedulerProvider extends ScheduledThreadPoolExecutorProvider { - public DefaultBackgroundSchedulerProvider() { - super("jersey-background-task-scheduler"); + public DefaultBackgroundSchedulerProvider(Configuration configuration) { + super("jersey-background-task-scheduler", configuration); } @Override diff --git a/docs/src/main/docbook/appendix-properties.xml b/docs/src/main/docbook/appendix-properties.xml index 9a1b29dab1..45595294a7 100644 --- a/docs/src/main/docbook/appendix-properties.xml +++ b/docs/src/main/docbook/appendix-properties.xml @@ -202,6 +202,39 @@ + + &jersey.common.CommonProperties.THREAD_FACTORY;(Jersey 2.44 or later) + + + jersey.config.threads.factory + + + + Defines the java.util.concurrent.ThreadFactory to be used by internal default + ExecutorServices. + + + The default is java.util.concurrent.Executors#defaultThreadFactory() on + platform threads andThread.ofVirtual().factory() on virtual threads. + + + + + &jersey.common.CommonProperties.USE_VIRTUAL_THREADS;(Jersey 2.44 or later) + + + jersey.config.threads.use.virtual + + + + Defines whether the virtual threads should be used by Jersey on JDK 21+ when not using an exact number + of threads by FixedThreadPool. + + + The default is &lit.false; for this version of Jersey, and &lit.true; for Jersey 3.1+. + + + &jersey.logging.LoggingFeature.LOGGING_FEATURE_LOGGER_NAME; @@ -1172,7 +1205,7 @@ &jersey.client.ClientProperties.SNI_HOST_NAME; (Jersey 2.43 or later) - jersey.config.client.snihostname + jersey.config.client.sniHostName Sets the host name to be used for calculating the javax.net.ssl.SNIHostName @@ -2296,6 +2329,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/dependencies.xml b/docs/src/main/docbook/dependencies.xml index 889eceab93..5583564290 100644 --- a/docs/src/main/docbook/dependencies.xml +++ b/docs/src/main/docbook/dependencies.xml @@ -77,6 +77,21 @@ +

+ Virtual Threads and Thread Factories + + With JDK 21 and above, Jersey (since 2.44) has the ability to use virtual threads instead of + the CachedThreadPool in the internal ExecutorServices. + Jersey also has the ability to specify the backing ThreadFactory for the + default ExecutorServices (the default ExecutorServices + can be overridden by the &jersey.common.spi.ExecutorServiceProvider; SPI). + + + To enable virtual threads and/or specify the ThreadFactory, use + &jersey.common.CommonProperties.USE_VIRTUAL_THREADS; and/or &jersey.common.CommonProperties.THREAD_FACTORY; + properties, respectively. See also the in appendix for property details. + +
Introduction to Jersey dependencies diff --git a/docs/src/main/docbook/jersey.ent b/docs/src/main/docbook/jersey.ent index 10d0b15c2e..1893612e5c 100644 --- a/docs/src/main/docbook/jersey.ent +++ b/docs/src/main/docbook/jersey.ent @@ -416,6 +416,8 @@ CommonProperties.JSON_JACKSON_DISABLED_MODULES_CLIENT" > CommonProperties.JSON_JACKSON_DISABLED_MODULES_SERVER" > CommonProperties.PARAM_CONVERTERS_THROW_IAE" > +CommonProperties.THREAD_FACTORY" > +CommonProperties.USE_VIRTUAL_THREADS" > DisposableSupplier"> InjectionManager"> InjectionResolver"> @@ -592,6 +594,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"> diff --git a/etc/jenkins/jenkins_build.sh b/etc/jenkins/jenkins_build.sh index 9983a05bf8..3543e90d35 100644 --- a/etc/jenkins/jenkins_build.sh +++ b/etc/jenkins/jenkins_build.sh @@ -2,4 +2,4 @@ export DEBUG=true -mvn -V -U -B -e -Pstaging clean install glassfish-copyright:check -Dcopyright.quiet=false \ No newline at end of file +mvn -V -U -B -e -Pstaging clean install glassfish-copyright:check -Dcopyright.quiet=false -DskipSBOM diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java index 1e81f529e6..2b7425f6dc 100644 --- a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java +++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2024 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2019, 2021 Payara Foundation and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the @@ -33,7 +33,6 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import java.util.logging.Level; @@ -66,6 +65,7 @@ import org.glassfish.jersey.client.Initializable; import org.glassfish.jersey.client.spi.ConnectorProvider; import org.glassfish.jersey.ext.cdi1x.internal.CdiUtil; +import org.glassfish.jersey.innate.VirtualThreadUtil; import org.glassfish.jersey.internal.ServiceFinder; import org.glassfish.jersey.internal.inject.InjectionManager; import org.glassfish.jersey.internal.inject.InjectionManagerSupplier; @@ -111,7 +111,7 @@ class RestClientBuilderImpl implements RestClientBuilder { asyncInterceptorFactories = new ArrayList<>(); config = ConfigProvider.getConfig(); configWrapper = new ConfigWrapper(clientBuilder.getConfiguration()); - executorService = Executors::newCachedThreadPool; + executorService = () -> VirtualThreadUtil.withConfig(configWrapper).newCachedThreadPool(); } @Override diff --git a/pom.xml b/pom.xml index 6045c837e6..66f4d9f2d1 100644 --- a/pom.xml +++ b/pom.xml @@ -428,12 +428,10 @@ junit.jupiter.execution.parallel.mode.default=same_thread - - - jersey.config.test.container.port - ${jersey.config.test.container.port} - - + + ${jersey.config.test.container.port} + + 124 @@ -635,12 +633,10 @@ ${skip.tests} ${skip.tests} ${failsafe.coverage.argline} - - - jersey.config.test.container.port - ${jersey.config.test.container.port} - - + + ${jersey.config.test.container.port} + + @@ -1103,6 +1099,37 @@ + + sbom + + + !skipSBOM + + + + + + org.cyclonedx + cyclonedx-maven-plugin + ${cyclonedx.mvn.plugin.version} + true + + + package + + makeAggregateBom + + + + framework + true + + + + + + + sonar @@ -2061,6 +2088,7 @@ but the jersey-common module which has to have the separate version for OSGi reasons. --> 3.9.0 + 2.8.0 3.6.1 3.1.2 3.3.0 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 243466caa2..fe85e9c76f 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/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"; + +} diff --git a/tests/e2e-jdk-specifics/src/test/java/org/glassfish/jersey/tests/e2e/jdk21/ThreadFactoryUsageTest.java b/tests/e2e-jdk-specifics/src/test/java/org/glassfish/jersey/tests/e2e/jdk21/ThreadFactoryUsageTest.java new file mode 100644 index 0000000000..6739ba98cc --- /dev/null +++ b/tests/e2e-jdk-specifics/src/test/java/org/glassfish/jersey/tests/e2e/jdk21/ThreadFactoryUsageTest.java @@ -0,0 +1,54 @@ +/* + * 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.tests.e2e.jdk21; + +import org.glassfish.jersey.CommonProperties; +import org.glassfish.jersey.innate.VirtualThreadSupport; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.ClientRequestFilter; +import jakarta.ws.rs.core.Response; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +public class ThreadFactoryUsageTest { + @Test + public void testThreadFactory() throws ExecutionException, InterruptedException { + CountDownLatch countDownLatch = new CountDownLatch(1); + ThreadFactory threadFactory = VirtualThreadSupport.allowVirtual(true).getThreadFactory(); + ThreadFactory countDownThreadFactory = r -> { + countDownLatch.countDown(); + return threadFactory.newThread(r); + }; + + CompletionStage r = ClientBuilder.newClient() + .property(CommonProperties.THREAD_FACTORY, countDownThreadFactory) + .property(CommonProperties.USE_VIRTUAL_THREADS, true) + .register((ClientRequestFilter) requestContext -> requestContext.abortWith(Response.ok().build())) + .target("http://localhost:58080/test").request().rx().get(); + + MatcherAssert.assertThat(r.toCompletableFuture().get().getStatus(), Matchers.is(200)); + countDownLatch.await(10, TimeUnit.SECONDS); + MatcherAssert.assertThat(countDownLatch.getCount(), Matchers.is(0L)); + } +}