From 09c8d62064d60df5f46692e68ca880d30a023d2b Mon Sep 17 00:00:00 2001 From: jansupol <15908245+jansupol@users.noreply.github.com> Date: Fri, 26 Apr 2024 07:52:10 +0200 Subject: [PATCH] Document a workaround for HTTP Patch & provide tests (#5622) Signed-off-by: jansupol --- .../client/HttpUrlConnectorProvider.java | 8 +- docs/src/main/docbook/appendix-properties.xml | 82 +++++++++ docs/src/main/docbook/client.xml | 162 ++++++++++-------- docs/src/main/docbook/dependencies.xml | 11 +- docs/src/main/docbook/jersey.ent | 1 + tests/e2e-jdk-specifics/pom.xml | 99 +++++++++++ .../jersey/tests/e2e/jdk17/HttpPatchTest.java | 72 ++++++++ tests/e2e-tls/pom.xml | 32 +++- .../e2e/tls/SslContextPerRequestTest.java | 72 +------- .../jersey/tests/e2e/tls/SslParentTest.java | 102 +++++++++++ .../tests/e2e/tls/patch/HttpsPatchTest.java | 81 +++++++++ tests/pom.xml | 1 + 12 files changed, 577 insertions(+), 146 deletions(-) create mode 100644 tests/e2e-jdk-specifics/pom.xml create mode 100644 tests/e2e-jdk-specifics/src/test/java/org/glassfish/jersey/tests/e2e/jdk17/HttpPatchTest.java create mode 100644 tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/SslParentTest.java create mode 100644 tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/patch/HttpsPatchTest.java diff --git a/core-client/src/main/java/org/glassfish/jersey/client/HttpUrlConnectorProvider.java b/core-client/src/main/java/org/glassfish/jersey/client/HttpUrlConnectorProvider.java index 00ffd75411..10dbe3da89 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/HttpUrlConnectorProvider.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/HttpUrlConnectorProvider.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 @@ -94,7 +94,7 @@ public class HttpUrlConnectorProvider implements ConnectorProvider { /** * A value of {@code true} declares that the client will try to set * unsupported HTTP method to {@link java.net.HttpURLConnection} via - * reflection. + * reflection as a workaround for a missing HTTP method. *

* NOTE: Enabling this property may cause security related warnings/errors * and it may break when other JDK implementation is used. Use only @@ -103,6 +103,10 @@ public class HttpUrlConnectorProvider implements ConnectorProvider { *

The value MUST be an instance of {@link java.lang.Boolean}.

*

The default value is {@code false}.

*

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

+ *

Since JDK 16 the JDK internal classes are not opened for reflection and the workaround method does not work, + * unless {@code --add-opens java.base/java.net=ALL-UNNAMED} for HTTP requests and additional + * {@code --add-opens java.base/sun.net.www.protocol.https=ALL-UNNAMED} for HTTPS (HttpsUrlConnection) options are set. + *

*/ public static final String SET_METHOD_WORKAROUND = "jersey.config.client.httpUrlConnection.setMethodWorkaround"; diff --git a/docs/src/main/docbook/appendix-properties.xml b/docs/src/main/docbook/appendix-properties.xml index f1d6140e78..b3a58aad99 100644 --- a/docs/src/main/docbook/appendix-properties.xml +++ b/docs/src/main/docbook/appendix-properties.xml @@ -1301,6 +1301,88 @@ + +
+ The default HttpUrlConnector properties + + List of properties defined in &jersey.client.HttpUrlConnectorProvider; class. + + + + List of the default &jersey.client.HttpUrlConnectorProvider; properites + + + + Constant + Value + Description + + + + + &jersey.client.HttpUrlConnectorProvider.SET_METHOD_WORKAROUND; + jersey.config.client.httpUrlConnection.setMethodWorkaround + + + A value of &lit.true; declares that the client will try to set + unsupported HTTP method to java.net.HttpURLConnection via + reflection as a workaround for a missing HTTP method. + + + NOTE: Enabling this property may cause security related warnings/errors + and it may break when other JDK implementation is used. Use only + when you know what you are doing. + + + The value MUST be an instance of &lit.jdk6.Boolean;. The default value is &lit.false;. + + Since JDK 16 the JDK internal classes are not opened for reflection and the workaround method + does not work, unless --add-opens java.base/java.net=ALL-UNNAMED for HTTP + requests and additional --add-opens java.base/sun.net.www.protocol.https=ALL-UNNAMED + for HTTPS (javax.net.ssl.HttpsUrlConnection) options are set. + + + + + &jersey.client.HttpUrlConnectorProvider.USE_FIXED_LENGTH_STREAMING; + jersey.config.client.httpUrlConnector.useFixedLengthStreaming + + + If &lit.true;, the &lit.jersey.client.HttpUrlConnector; (if used) will assume the content length + from the value of javax.ws.rs.core.HttpHeaders#CONTENT_LENGTH request + header (if present). + + + When this property is enabled and the request has a valid non-zero content length + value specified in its javax.ws.rs.core.HttpHeaders#CONTENT_LENGTH request + header, that this value will be used as an input to the + java.net.HttpURLConnection#setFixedLengthStreamingMode(int) method call + invoked on the underlying java.net.HttpURLConnection. + This will also suppress the entity buffering in the HttpURLConnection, + which is undesirable in certain scenarios, e.g. when streaming large entities. + + + Note that the content length value defined in the request header must exactly match + the real size of the entity. If the javax.ws.rs.core.HttpHeaders#CONTENT_LENGTH + header is explicitly specified in a request, this property will be ignored and the + request entity will be still buffered by the underlying HttpURLConnection infrastructure. + + + This property also overrides the behaviour enabled by the &jersey.client.ClientProperties.CHUNKED_ENCODING_SIZE; + property. Chunked encoding will only be used, if the size is not specified in the header of the request. + + + Note that this property only applies to client run-times that are configured to use the default + &lit.jersey.client.HttpUrlConnector; as the client connector. The property is ignored by other connectors. + + + The default value is &lit.false;. + + + + + +
Apache HTTP client configuration properties diff --git a/docs/src/main/docbook/client.xml b/docs/src/main/docbook/client.xml index af8f894c80..a305161827 100644 --- a/docs/src/main/docbook/client.xml +++ b/docs/src/main/docbook/client.xml @@ -678,71 +678,6 @@ webTarget.request().post(Entity.entity(f, MediaType.TEXT_PLAIN_TYPE)); - - - - Be aware of using other than default &lit.jersey.client.Connector; implementation. - There is an issue handling HTTP headers in - &lit.jaxrs.WriterInterceptor; or &lit.jaxrs.ext.MessageBodyWriter;. - If you need to change header fields do not use nor - &lit.jersey.apache.ApacheConnectorProvider; nor &lit.jersey.grizzly.GrizzlyConnectorProvider; - nor &lit.jersey.jetty.JettyConnectorProvider; neither &lit.jersey.netty.NettyConnectorProvider;. - The issue for example applies to Jersey - feature that also modifies HTTP headers. - - - On the other hand, in the default transport connector, there are some restrictions on the headers, that - can be sent in the default configuration. - HttpUrlConnectorProvider uses &lit.jdk6.HttpUrlConnection; as an underlying connection - implementation. This JDK class by default restricts the use of following headers: - - &lit.http.header.AccessControlRequestHeaders; - &lit.http.header.AccessControlRequestMethod; - &lit.http.header.Connection; (with one exception - &lit.http.header.Connection; header with - value Closed is allowed by default) - &lit.http.header.ContentLength; - &lit.http.header.ContentTransferEncoding; - &lit.http.header.Host; - &lit.http.header.Keep-Alive; - &lit.http.header.Origin; - &lit.http.header.Trailer; - &lit.http.header.Transfer-Encoding; - &lit.http.header.Upgrade; - &lit.http.header.Via; - all the headers starting with &lit.http.header.Sec.prefix; - - The underlying connection can be configured to permit all headers to be sent, - however this behaviour can be changed only by setting the system property - sun.net.http.allowRestrictedHeaders. - - Sending restricted headers with <literal>HttpUrlConnector</literal> - - Client client = ClientBuilder.newClient(); - System.setProperty("sun.net.http.allowRestrictedHeaders", "true"); - - Response response = client.target(yourUri).path(yourPath).request(). - header("Origin", "http://example.com"). - header("Access-Control-Request-Method", "POST"). - get(); - - - - - Note, that internally the &lit.jdk6.HttpUrlConnection; instances are pooled, so (un)setting the - property after already creating a target typically does not have any effect. - The property influences all the connections created after the property has been - (un)set, but there is no guarantee, that your request will use a connection - created after the property change. - - - In a simple environment, setting the property before creating the first target is sufficient, but in complex - environments (such as application servers), where some poolable connections might exist before your - application even bootstraps, this approach is not 100% reliable and we recommend using a different client - transport connector, such as Apache Connector. - These limitations have to be considered especially when invoking CORS (Cross Origin - Resource Sharing) requests. - - As indicated earlier, &jersey.client.Connector; and &jersey.client.ConnectorProvider; contracts are Jersey-specific @@ -751,8 +686,8 @@ webTarget.request().post(Entity.entity(f, MediaType.TEXT_PLAIN_TYPE)); instance: ClientConfig clientConfig = new ClientConfig(); -clientConfig.connectorProvider(new GrizzlyConnectorProvider()); -Client client = ClientBuilder.newClient(clientConfig); + clientConfig.connectorProvider(new GrizzlyConnectorProvider()); + Client client = ClientBuilder.newClient(clientConfig); &lit.jaxrs.client.Client; accepts as a constructor argument a &lit.jaxrs.core.Configurable; instance. Jersey implementation of the &lit.jaxrs.core.Configurable; provider for the client is &lit.jersey.client.ClientConfig;. @@ -771,11 +706,96 @@ Client client = ClientBuilder.newClient(clientConfig); Jersey 2.40. The following example shows how to setup the custom Grizzly Asynchronous HTTP Client based &lit.jersey.client.ConnectorProvider; in a Jersey client instance: Client client = ClientBuilder.newBuilder() - .property(ClientProperties.CONNECTOR_PROVIDER, "org.glassfish.jersey.grizzly.connector.GrizzlyConnectorProvider") - .build(); + .property(ClientProperties.CONNECTOR_PROVIDER, "org.glassfish.jersey.grizzly.connector.GrizzlyConnectorProvider") + .build(); For more information about the property see . + + + + Be aware of using other than default &lit.jersey.client.Connector; implementation. + There is an issue handling HTTP headers in + &lit.jaxrs.WriterInterceptor; or &lit.jaxrs.ext.MessageBodyWriter;. + If you need to change header fields do not use neither + &lit.jersey.apache.ApacheConnectorProvider; nor &lit.jersey.grizzly.GrizzlyConnectorProvider; + nor &lit.jersey.jetty.JettyConnectorProvider; (for asynchronous requests). + Other older version connectors can be affected by this issue as well. + The issue for example applies to Jersey + feature that also modifies HTTP headers. + + + +
+ The default &lit.jersey.client.HttpUrlConnector; + + The default connector is the most advanced connector, and it supports the most features Jersey has to offer. + However, there are a few limitations coming from the &lit.jdk6.HttpUrlConnection;. + + One limitation is in the variety of HTTP methods supported by the &lit.jdk6.HttpUrlConnection;, since only the + original HTTP/1.1 methods are supported. For instance, HTTP Patch method is not supported. See + property &jersey.client.HttpUrlConnectorProvider.SET_METHOD_WORKAROUND; in the Appendix + for a possible workaround. + + + Also, in the default transport connector, there are some restrictions on the headers, that + can be sent in the default configuration. + HttpUrlConnectorProvider uses &lit.jdk6.HttpUrlConnection; as an underlying connection + implementation. This JDK class by default restricts the use of following headers: + + &lit.http.header.AccessControlRequestHeaders; + &lit.http.header.AccessControlRequestMethod; + &lit.http.header.Connection; (with one exception - &lit.http.header.Connection; header with + value Closed is allowed by default) + &lit.http.header.ContentLength; + &lit.http.header.ContentTransferEncoding; + &lit.http.header.Host; + &lit.http.header.Keep-Alive; + &lit.http.header.Origin; + &lit.http.header.Trailer; + &lit.http.header.Transfer-Encoding; + &lit.http.header.Upgrade; + &lit.http.header.Via; + all the headers starting with &lit.http.header.Sec.prefix; + + The underlying connection can be configured to permit all headers to be sent, + however this behaviour can be changed only by setting the system property + sun.net.http.allowRestrictedHeaders. + + Sending restricted headers with <literal>HttpUrlConnector</literal> + + Client client = ClientBuilder.newClient(); + System.setProperty("sun.net.http.allowRestrictedHeaders", "true"); + + Response response = client.target(yourUri).path(yourPath).request(). + header("Origin", "http://example.com"). + header("Access-Control-Request-Method", "POST"). + get(); + + + + + Internally, the &lit.jdk6.HttpUrlConnection; instances are pooled, so (un)setting the + property after already creating a target typically does not have any effect. + The property influences all the connections created after the property has been + (un)set, but there is no guarantee, that your request will use a connection + created after the property change. + + + In a simple environment, setting the property before creating the first target is sufficient, but in complex + environments (such as application servers), where some poolable connections might exist before your + application even bootstraps, this approach is not 100% reliable and we recommend using a different client + transport connector, such as Apache Connector. + These limitations have to be considered especially when invoking CORS (Cross Origin + Resource Sharing) requests. + + + + + The limited configurability of the &lit.jdk6.HttpUrlConnection; is another aspect to consider. For details, see + Java Networking Properties, for instance property http.maxConnections. + +
Client Connectors Properties @@ -931,7 +951,7 @@ System.out.println("Now the connection is closed."); To solve injection of a custom type into a client provider instance use &jersey.client.InjectionManagerClientProvider; to - extract &hk2.ServiceLocator; which can return the required injection. The following example shows how to utilize + extract &lit.jersey.common.internal.inject.InjectionManager; which can return the required injection. The following example shows how to utilize &lit.jersey.client.InjectionManagerClientProvider;: @@ -956,7 +976,7 @@ System.out.println("Now the connection is closed."); For more information see javadoc of &jersey.client.InjectionManagerClientProvider; - (and javadoc of &lit.jersey.common.InjectionManagerProvider; which supports common JAX-RS components). + (and javadoc of &lit.jersey.common.InjectionManagerProvider; which supports common Jakarta REST components).
diff --git a/docs/src/main/docbook/dependencies.xml b/docs/src/main/docbook/dependencies.xml index ad40a92fe5..b58ebd8016 100644 --- a/docs/src/main/docbook/dependencies.xml +++ b/docs/src/main/docbook/dependencies.xml @@ -1,7 +1,7 @@ + + + 4.0.0 + + + org.glassfish.jersey.tests + project + 2.43-SNAPSHOT + + + e2e-jdk-specifics + jersey-tests-e2e-specifics + jar + + Jersey E2E tests for testing JDK 17+ specifics + + + + + org.apache.maven.plugins + maven-surefire-plugin + + 1 + false + false + ${skip.e2e} + + ${http.patch.addopens} + + + + + + + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-bundle + pom + test + + + org.glassfish.jersey.test-framework + jersey-test-framework-util + test + + + org.hamcrest + hamcrest + test + + + org.junit.platform + junit-platform-suite + ${junit-platform-suite.version} + test + + + + + + jdk15- + + [8, 16) + + + + + + + jdk16+ + + [16, ) + + + --add-opens java.base/java.net=ALL-UNNAMED + + + + + diff --git a/tests/e2e-jdk-specifics/src/test/java/org/glassfish/jersey/tests/e2e/jdk17/HttpPatchTest.java b/tests/e2e-jdk-specifics/src/test/java/org/glassfish/jersey/tests/e2e/jdk17/HttpPatchTest.java new file mode 100644 index 0000000000..ac63c3d43f --- /dev/null +++ b/tests/e2e-jdk-specifics/src/test/java/org/glassfish/jersey/tests/e2e/jdk17/HttpPatchTest.java @@ -0,0 +1,72 @@ +/* + * 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.jdk17; + +import org.glassfish.jersey.client.HttpUrlConnectorProvider; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; + +import org.junit.jupiter.api.Test; + +import javax.ws.rs.HttpMethod; +import javax.ws.rs.Path; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.Response; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +public class HttpPatchTest extends JerseyTest { + + public static final String PATCH_ENTITY = "HelloPatch"; + + @Target({ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @HttpMethod(HttpMethod.PATCH) + public @interface PATCH { + + } + + @Path("/") + public static class HttpPatchResource { + @PATCH + public String patchEcho(String entity) { + return entity; + } + } + + @Override + protected Application configure() { + return new ResourceConfig(HttpPatchResource.class); + } + + @Test + void testPatchWithHttpUrlConnector() { + try (Response response = target() + .property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true) + .request().method(HttpMethod.PATCH, Entity.text(PATCH_ENTITY))) { + MatcherAssert.assertThat(200, Matchers.equalTo(response.getStatus())); + response.bufferEntity(); + System.out.println(response.readEntity(String.class)); + MatcherAssert.assertThat(PATCH_ENTITY, Matchers.equalTo(response.readEntity(String.class))); + } + } +} diff --git a/tests/e2e-tls/pom.xml b/tests/e2e-tls/pom.xml index 5248fe40c2..8f4ef240f5 100644 --- a/tests/e2e-tls/pom.xml +++ b/tests/e2e-tls/pom.xml @@ -40,7 +40,6 @@ 1 false - false ${skip.e2e} true @@ -50,6 +49,24 @@ + + + HttpsPatch + + test + + + + **/HttpsPatchTest.java + + + true + + + ${http.patch.addopens} + + + @@ -118,6 +135,15 @@ + + jdk8 + + 8 + + + + + jdk11+ @@ -133,6 +159,10 @@ -Djdk.tls.server.protocols=TLSv1.2 + + --add-opens java.base/java.net=ALL-UNNAMED + --add-opens java.base/sun.net.www.protocol.https=ALL-UNNAMED + diff --git a/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/SslContextPerRequestTest.java b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/SslContextPerRequestTest.java index 9ae957e9a7..364da77394 100644 --- a/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/SslContextPerRequestTest.java +++ b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/SslContextPerRequestTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -53,10 +53,8 @@ import java.util.function.Supplier; import java.util.stream.Stream; -public class SslContextPerRequestTest extends JerseyTest { +public class SslContextPerRequestTest extends SslParentTest { - private SSLContext serverSslContext; - private SSLParameters serverSslParameters; private static final String MESSAGE = "Message for Netty with SSL"; @Override @@ -77,33 +75,6 @@ protected Application configure() { return new ResourceConfig(TestResource.class); } - @Override - protected URI getBaseUri() { - return UriBuilder - .fromUri("https://localhost") - .port(getPort()) - .build(); - } - - @Override - protected Optional getSslContext() { - if (serverSslContext == null) { - serverSslContext = SslUtils.createServerSslContext(); - } - - return Optional.of(serverSslContext); - } - - @Override - protected Optional getSslParameters() { - if (serverSslParameters == null) { - serverSslParameters = new SSLParameters(); - serverSslParameters.setNeedClientAuth(false); - } - - return Optional.of(serverSslParameters); - } - public static Stream connectorProviders() { return Stream.of( new HttpUrlConnectorProvider(), @@ -168,43 +139,4 @@ public void testSslOnClient(ConnectorProvider connectorProvider) { String s = target.request().get(String.class); Assertions.assertEquals(MESSAGE, s); } - - private static class SslUtils { - - private static final String SERVER_IDENTITY_PATH = "server-identity.jks"; - private static final char[] SERVER_IDENTITY_PASSWORD = "secret".toCharArray(); - - private static final String CLIENT_TRUSTSTORE_PATH = "client-truststore.jks"; - private static final char[] CLIENT_TRUSTSTORE_PASSWORD = "secret".toCharArray(); - - private static final String KEYSTORE_TYPE = "PKCS12"; - - private SslUtils() {} - - public static SSLContext createServerSslContext() { - return new SslContextClientBuilder() - .keyStore(getKeyStore(SERVER_IDENTITY_PATH, SERVER_IDENTITY_PASSWORD), SERVER_IDENTITY_PASSWORD) - .get(); - } - - public static Supplier createClientSslContext() { - return new SslContextClientBuilder() - .trustStore(getKeyStore(CLIENT_TRUSTSTORE_PATH, CLIENT_TRUSTSTORE_PASSWORD)); - - } - - private static KeyStore getKeyStore(String path, char[] keyStorePassword) { - try (InputStream inputStream = getResource(path)) { - KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE); - keyStore.load(inputStream, keyStorePassword); - return keyStore; - } catch (Exception e) { - throw new ProcessingException(e); - } - } - - private static InputStream getResource(String path) { - return SslUtils.class.getClassLoader().getResourceAsStream(path); - } - } } diff --git a/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/SslParentTest.java b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/SslParentTest.java new file mode 100644 index 0000000000..9e9aee29c9 --- /dev/null +++ b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/SslParentTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2023, 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.tls; + +import org.glassfish.jersey.client.SslContextClientBuilder; +import org.glassfish.jersey.test.JerseyTest; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import javax.ws.rs.ProcessingException; +import javax.ws.rs.core.UriBuilder; +import java.io.InputStream; +import java.net.URI; +import java.security.KeyStore; +import java.util.Optional; +import java.util.function.Supplier; + +public class SslParentTest extends JerseyTest { + + protected SSLContext serverSslContext; + protected SSLParameters serverSslParameters; + + @Override + protected Optional getSslContext() { + if (serverSslContext == null) { + serverSslContext = SslUtils.createServerSslContext(); + } + + return Optional.of(serverSslContext); + } + + @Override + protected Optional getSslParameters() { + if (serverSslParameters == null) { + serverSslParameters = new SSLParameters(); + serverSslParameters.setNeedClientAuth(false); + } + + return Optional.of(serverSslParameters); + } + + @Override + protected URI getBaseUri() { + return UriBuilder + .fromUri("https://localhost") + .port(getPort()) + .build(); + } + + protected static class SslUtils { + + private static final String SERVER_IDENTITY_PATH = "server-identity.jks"; + private static final char[] SERVER_IDENTITY_PASSWORD = "secret".toCharArray(); + + private static final String CLIENT_TRUSTSTORE_PATH = "client-truststore.jks"; + private static final char[] CLIENT_TRUSTSTORE_PASSWORD = "secret".toCharArray(); + + private static final String KEYSTORE_TYPE = "PKCS12"; + + private SslUtils() {} + + public static SSLContext createServerSslContext() { + return new SslContextClientBuilder() + .keyStore(getKeyStore(SERVER_IDENTITY_PATH, SERVER_IDENTITY_PASSWORD), SERVER_IDENTITY_PASSWORD) + .get(); + } + + public static Supplier createClientSslContext() { + return new SslContextClientBuilder() + .trustStore(getKeyStore(CLIENT_TRUSTSTORE_PATH, CLIENT_TRUSTSTORE_PASSWORD)); + + } + + private static KeyStore getKeyStore(String path, char[] keyStorePassword) { + try (InputStream inputStream = getResource(path)) { + KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE); + keyStore.load(inputStream, keyStorePassword); + return keyStore; + } catch (Exception e) { + throw new ProcessingException(e); + } + } + + private static InputStream getResource(String path) { + return SslUtils.class.getClassLoader().getResourceAsStream(path); + } + } +} diff --git a/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/patch/HttpsPatchTest.java b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/patch/HttpsPatchTest.java new file mode 100644 index 0000000000..fd6fe4798e --- /dev/null +++ b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/patch/HttpsPatchTest.java @@ -0,0 +1,81 @@ +/* + * 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.tls.patch; + +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.HttpUrlConnectorProvider; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.tests.e2e.tls.SslParentTest; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import javax.net.ssl.SSLContext; +import javax.ws.rs.HttpMethod; +import javax.ws.rs.Path; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.Response; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.function.Supplier; + +public class HttpsPatchTest extends SslParentTest { + + public static final String PATCH_ENTITY = "HelloPatch"; + + @Target({ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @HttpMethod(HttpMethod.PATCH) + public @interface PATCH { + + } + + @Path("/") + public static class HttpPatchResource { + @PATCH + public String patchEcho(String entity) { + return entity; + } + } + + @Override + protected Application configure() { + return new ResourceConfig(HttpPatchResource.class); + } + + @Test + void testPatchWithHttpUrlConnector() { + String value = System.getProperty("jersey.added.opens"); + if (null == value) { + System.out.println("JDK add-opens not set - exiting..."); + return; + } + + Supplier clientSslContext = SslUtils.createClientSslContext(); + try (Response response = target() + .property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true) + .property(ClientProperties.SSL_CONTEXT_SUPPLIER, clientSslContext) + .request().method(HttpMethod.PATCH, Entity.text(PATCH_ENTITY))) { + MatcherAssert.assertThat(200, Matchers.equalTo(response.getStatus())); + response.bufferEntity(); + System.out.println(response.readEntity(String.class)); + MatcherAssert.assertThat(PATCH_ENTITY, Matchers.equalTo(response.readEntity(String.class))); + } + } +} diff --git a/tests/pom.xml b/tests/pom.xml index ca326b2551..5f44b938ed 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -42,6 +42,7 @@ e2e-core-common e2e-entity e2e-inject + e2e-jdk-specifics e2e-server e2e-testng e2e-tls