diff --git a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java b/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java index 7c82c4128cc..a743d538fe1 100644 --- a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java +++ b/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java @@ -45,6 +45,8 @@ import javax.net.ssl.SSLContext; +import org.eclipse.jetty.client.HttpClientTransport; +import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; import org.eclipse.jetty.client.util.BasicAuthentication; import org.eclipse.jetty.client.util.BytesContentProvider; import org.eclipse.jetty.client.util.FutureResponseListener; @@ -144,19 +146,13 @@ class JettyConnector implements Connector { */ JettyConnector(final Client jaxrsClient, final Configuration config) { this.configuration = config; - HttpClient httpClient = null; - if (config.isRegistered(JettyHttpClientSupplier.class)) { - Optional contract = config.getInstances().stream() - .filter(a-> JettyHttpClientSupplier.class.isInstance(a)).findFirst(); - if (contract.isPresent()) { - httpClient = ((JettyHttpClientSupplier) contract.get()).getHttpClient(); - } - } + HttpClient httpClient = getRegisteredHttpClient(config); + if (httpClient == null) { final SSLContext sslContext = jaxrsClient.getSslContext(); final SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(false); sslContextFactory.setSslContext(sslContext); - httpClient = new HttpClient(sslContextFactory); + httpClient = new HttpClient(initClientTransport(), sslContextFactory); } this.client = httpClient; @@ -224,6 +220,37 @@ class JettyConnector implements Connector { this.cookieStore = client.getCookieStore(); } + /** + * provides required HTTP client transport for client + * + * the default transport is {@link HttpClientTransportOverHTTP} + * + * @return instance of {@link HttpClientTransport} + * @since 2.40 + */ + HttpClientTransport initClientTransport() { + return new HttpClientTransportOverHTTP(); + } + + /** + * provides custom registered {@link HttpClient} if any (or NULL) + * + * @param config configuration where {@link HttpClient} could be registered + * @return {@link HttpClient} instance if any was previously registered or NULL + * + * @since 2.40 + */ + HttpClient getRegisteredHttpClient(Configuration config) { + if (config.isRegistered(JettyHttpClientSupplier.class)) { + Optional contract = config.getInstances().stream() + .filter(a-> JettyHttpClientSupplier.class.isInstance(a)).findFirst(); + if (contract.isPresent()) { + return ((JettyHttpClientSupplier) contract.get()).getHttpClient(); + } + } + return null; + } + /** * Get the {@link HttpClient}. * diff --git a/connectors/jetty-http2-connector/pom.xml b/connectors/jetty-http2-connector/pom.xml new file mode 100644 index 00000000000..c66634e8981 --- /dev/null +++ b/connectors/jetty-http2-connector/pom.xml @@ -0,0 +1,138 @@ + + + + + 4.0.0 + + + org.glassfish.jersey.connectors + project + 2.40-SNAPSHOT + + + jersey-jetty-http2-connector + jar + jersey-connectors-jetty-http2 + + Jersey Client Transport via Jetty + + + UTF-8 + + + + + org.eclipse.jetty + jetty-client + + + org.eclipse.jetty.http2 + http2-client + + + org.eclipse.jetty.http2 + http2-http-client-transport + + + org.eclipse.jetty + jetty-util + + + + org.glassfish.jersey.connectors + jersey-jetty-connector + ${project.version} + + + + org.glassfish.jersey.media + jersey-media-jaxb + ${project.version} + test + + + + org.glassfish.jersey.containers + jersey-container-jetty-http + ${project.version} + test + + + org.glassfish.jersey.media + jersey-media-json-jackson + ${project.version} + test + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-jetty + ${project.version} + test + + + + + + + com.sun.istack + istack-commons-maven-plugin + true + + + org.codehaus.mojo + build-helper-maven-plugin + true + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.felix + maven-bundle-plugin + true + + + + ${jetty.osgi.version}, + * + + + + + + + + + + jdk11+ + + [11,) + + + + com.sun.xml.bind + jaxb-osgi + test + + + + + + diff --git a/connectors/jetty-http2-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttp2ClientSupplier.java b/connectors/jetty-http2-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttp2ClientSupplier.java new file mode 100644 index 00000000000..c58a770ebad --- /dev/null +++ b/connectors/jetty-http2-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttp2ClientSupplier.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 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.jetty.connector; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpClientTransport; +import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; + +/** + * HTTP/2 enabled version of the {@link JettyHttpClientSupplier} + * + * @since 2.40 + */ +public class JettyHttp2ClientSupplier implements JettyHttpClientContract { + private final HttpClient http2Client; + + /** + * default Http2Client created for the supplier. + */ + public JettyHttp2ClientSupplier() { + this(createHttp2Client()); + } + /** + * supplier for the {@code HttpClient} with {@code HttpClientTransportOverHTTP2} to be optionally registered + * to a {@link org.glassfish.jersey.client.ClientConfig} + * @param http2Client a HttpClient to be supplied when {@link JettyConnector#getHttpClient()} is called. + */ + public JettyHttp2ClientSupplier(HttpClient http2Client) { + this.http2Client = http2Client; + } + + private static final HttpClient createHttp2Client() { + final HttpClientTransport transport = new HttpClientTransportOverHTTP2(new HTTP2Client()); + return new HttpClient(transport); + } + + @Override + public HttpClient getHttpClient() { + return http2Client; + } +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttp2Connector.java b/connectors/jetty-http2-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttp2Connector.java new file mode 100644 index 00000000000..581b82bea2a --- /dev/null +++ b/connectors/jetty-http2-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttp2Connector.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2023 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.jetty.connector; + +import javax.ws.rs.client.Client; +import javax.ws.rs.core.Configuration; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpClientTransport; +import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; + +import java.util.Optional; + +/** + * Extends {@link JettyConnector} with HTTP/2 transport support + * + * @since 2.40 + */ +class JettyHttp2Connector extends JettyConnector { + + + /** + * Create the new Jetty HTTP/2 client connector. + * + * @param jaxrsClient JAX-RS client instance, for which the connector is created. + * @param config client configuration. + */ + JettyHttp2Connector(Client jaxrsClient, Configuration config) { + super(jaxrsClient, config); + } + + /** + * provides required {@link HttpClientTransport} for client + * + * The overriden method provides {@link HttpClientTransportOverHTTP2} with initialized {@link HTTP2Client} + * + * @return {@link HttpClientTransportOverHTTP2} + * @since 2.40 + */ + @Override + HttpClientTransport initClientTransport() { + return new HttpClientTransportOverHTTP2(new HTTP2Client()); + } + + /** + * provides custom registered {@link HttpClient} (if any) with HTTP/2 support + * + * @param config configuration where {@link HttpClient} could be registered + * @return {@link HttpClient} instance if any was previously registered or NULL + * + * @since 2.40 + */ + @Override + HttpClient getRegisteredHttpClient(Configuration config) { + if (config.isRegistered(JettyHttp2ClientSupplier.class)) { + Optional contract = config.getInstances().stream() + .filter(a-> JettyHttp2ClientSupplier.class.isInstance(a)).findFirst(); + if (contract.isPresent()) { + return ((JettyHttp2ClientSupplier) contract.get()).getHttpClient(); + } + } + return null; + } +} diff --git a/connectors/jetty-http2-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttp2ConnectorProvider.java b/connectors/jetty-http2-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttp2ConnectorProvider.java new file mode 100644 index 00000000000..67b0d6e6769 --- /dev/null +++ b/connectors/jetty-http2-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttp2ConnectorProvider.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 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.jetty.connector; + +import javax.ws.rs.client.Client; +import javax.ws.rs.core.Configurable; +import javax.ws.rs.core.Configuration; +import org.eclipse.jetty.client.HttpClient; +import org.glassfish.jersey.client.Initializable; +import org.glassfish.jersey.client.spi.Connector; + +/** + * Provides HTTP2 enabled version of the {@link JettyConnectorProvider} for a client + * + * @since 2.40 + */ +public class JettyHttp2ConnectorProvider extends JettyConnectorProvider { + @Override + public Connector getConnector(Client client, Configuration runtimeConfig) { + return new JettyHttp2Connector(client, runtimeConfig); + } + + public static HttpClient getHttpClient(Configurable component) { + if (!(component instanceof Initializable)) { + throw new IllegalArgumentException( + LocalizationMessages.INVALID_CONFIGURABLE_COMPONENT_TYPE(component.getClass().getName())); + } + + final Initializable initializable = (Initializable) component; + Connector connector = initializable.getConfiguration().getConnector(); + if (connector == null) { + initializable.preInitialize(); + connector = initializable.getConfiguration().getConnector(); + } + + if (connector instanceof JettyHttp2Connector) { + return ((JettyHttp2Connector) connector).getHttpClient(); + } + + throw new IllegalArgumentException(LocalizationMessages.EXPECTED_CONNECTOR_PROVIDER_NOT_USED()); + } +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/main/java/org/glassfish/jersey/jetty/connector/package-info.java b/connectors/jetty-http2-connector/src/main/java/org/glassfish/jersey/jetty/connector/package-info.java new file mode 100644 index 00000000000..0eeb19661dc --- /dev/null +++ b/connectors/jetty-http2-connector/src/main/java/org/glassfish/jersey/jetty/connector/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2013, 2018 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 HTTP2 client {@link org.glassfish.jersey.client.spi.Connector connector} based on the + * Jetty Client. + */ +package org.glassfish.jersey.jetty.connector; diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/connector/Http2PresenceTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/connector/Http2PresenceTest.java new file mode 100644 index 00000000000..af40c683d0b --- /dev/null +++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/connector/Http2PresenceTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2023 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.jetty.connector; + +import java.util.List; +import java.util.logging.Logger; + +import javax.ws.rs.GET; +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.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/** + * Tests the HTTP2 presence. + * + */ +@Disabled +public class Http2PresenceTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(Http2PresenceTest.class.getName()); + + @Path("/test") + public static class HttpMethodResource { + @POST + public String post( + @HeaderParam("Transfer-Encoding") String transferEncoding, + @HeaderParam("X-CLIENT") String xClient, + @HeaderParam("X-WRITER") String xWriter, + String entity) { + assertEquals("client", xClient); + return "POST"; + } + + @GET + public String testUserAgent(@Context HttpHeaders httpHeaders) { + final List requestHeader = httpHeaders.getRequestHeader(HttpHeaders.USER_AGENT); + if (requestHeader.size() != 1) { + return "FAIL"; + } + return requestHeader.get(0); + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(HttpMethodResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + @Test + public void testPost() { + Response response = target().path("test").request().header("X-CLIENT", "client").post(null); + + assertEquals(200, response.getStatus()); + assertTrue(response.hasEntity()); + } + + /** + * Test, that {@code User-agent} header is as set by Jersey, not by underlying Jetty client. + */ + @Test + public void testUserAgent() { + String response = target().path("test").request().get(String.class); + assertTrue(response.startsWith("Jersey"), "User-agent header should start with 'Jersey', but was " + response); + } +} diff --git a/connectors/pom.xml b/connectors/pom.xml index c2094dbfe91..cffebf1b48f 100644 --- a/connectors/pom.xml +++ b/connectors/pom.xml @@ -39,6 +39,7 @@ grizzly-connector jdk-connector jetty-connector + jetty-http2-connector netty-connector diff --git a/pom.xml b/pom.xml index 395d8b3f6e8..5de77aea1d7 100644 --- a/pom.xml +++ b/pom.xml @@ -1705,6 +1705,16 @@ jetty-client ${jetty.version} + + org.eclipse.jetty.http2 + http2-client + ${jetty.version} + + + org.eclipse.jetty.http2 + http2-http-client-transport + ${jetty.version} + org.eclipse.jetty jetty-server