diff --git a/connectors/grizzly-connector/pom.xml b/connectors/grizzly-connector/pom.xml index 73e5348649..a9327a5488 100644 --- a/connectors/grizzly-connector/pom.xml +++ b/connectors/grizzly-connector/pom.xml @@ -1,7 +1,7 @@ - + 4.0.0 @@ -32,6 +33,14 @@ Grizzly 2 Http Container. + + + JdkHttpClientThread + org.glassfish.jersey.grizzly2.httpserver.test.tools + ${clientImplPackage}.${client} + -Xms160m -Xmx160m -Xss512k + + jakarta.inject @@ -46,6 +55,40 @@ hamcrest-library test + + + org.glassfish.grizzly + grizzly-http2 + test + + + org.glassfish.grizzly + grizzly-npn-api + test + + + + org.eclipse.jetty + jetty-client + test + + + org.eclipse.jetty.http2 + http2-http-client-transport + ${jetty.version} + test + + + + org.bouncycastle + bcprov-jdk15on + test + + + org.bouncycastle + bcmail-jdk15on + test + @@ -65,7 +108,87 @@ maven-bundle-plugin true + + + maven-surefire-plugin + + + + ${testMemory} -XX:+CrashOnOutOfMemoryError -XX:-HeapDumpOnOutOfMemoryError + -DtestTime=10 -DclientCount=30 + -DclientImpl=${clientImpl} + + false + methods + 3 + + + + + jdk11 + + [11,) + + + + + + maven-compiler-plugin + + + default-testCompile + + 11 + 11 + + + + + + + + + + jdk8 + + 1.8 + + + + + + maven-compiler-plugin + + + default-testCompile + + + true + + + + + + + + + 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 7ddb2336b5..4eb458da4f 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, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022 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 diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerTest.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerTest.java new file mode 100644 index 0000000000..695c9060b0 --- /dev/null +++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerTest.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2021, 2022 Payara Foundation 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.grizzly2.httpserver; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.glassfish.jersey.grizzly2.httpserver.test.tools.ClientThread; +import org.glassfish.jersey.grizzly2.httpserver.test.tools.ClientThread.ClientThreadSettings; +import org.glassfish.jersey.grizzly2.httpserver.test.tools.JdkHttpClientThread; +import org.glassfish.jersey.grizzly2.httpserver.test.tools.ServerManager; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + + +/** + * Test verifying stability of the {@link GrizzlyHttpContainer} having to serve many requests + * and also giving several examples of how to configure HTTP, HTTPS and HTTP/2 clients. + *

+ * Created as an attempt to reproduce Grizzly's issue #2125 (GitHub) + * + * @author David Matejcek + */ +public class GrizzlyHttpServerTest { + + private static final String CLIENT_IMPLEMENTATION = System.getProperty("clientImpl", + JdkHttpClientThread.class.getCanonicalName()); + private static final long TIME_IN_MILLIS = Long.getLong("testTime", 10L) * 1000L; + private static final int COUNT_OF_CLIENTS = Integer.getInteger("clientCount", 20); + + private final List clients = new ArrayList<>(COUNT_OF_CLIENTS); + private AtomicReference error = new AtomicReference<>(); + private AtomicInteger counter; + private ServerManager server; + + @Rule + public TestName testName = new TestName(); + + @BeforeClass + public static void printSettings() { + System.out.println("Client implementation: " + CLIENT_IMPLEMENTATION); + System.out.println("Count of clients: " + COUNT_OF_CLIENTS); + System.out.println("Test duration: " + TIME_IN_MILLIS / 1000 + " s"); + } + + + @Before + public void init() { + this.counter = new AtomicInteger(); + } + + + @After + public void cleanup() { + error = null; + System.out.println(String.format("Server processed %s requests of test %s.", counter, testName.getMethodName())); + if (server != null) { + server.close(); + } + } + + + /** + * Test for unsecured HTTP 1.1 protocol. + * + * @throws Throwable + */ + @Test + public void http() throws Throwable { + final boolean secured = false; + final boolean useHttp2 = false; + this.server = new ServerManager(secured, useHttp2); + this.clients.addAll(createClients(secured, useHttp2)); + executeTest(); + } + + + /** + * Test for HTTP 1.1 protocol encrypted by {@value ClientThread#ENCRYPTION_PROTOCOL}. + * + * @throws Throwable + */ + @Test + public void https() throws Throwable { + final boolean secured = true; + final boolean useHttp2 = false; + this.server = new ServerManager(secured, useHttp2); + this.clients.addAll(createClients(secured, useHttp2)); + executeTest(); + } + + + /** + * This test is rather for documentaion purpose, because HTTP/2 is usually not allowed to be + * used without encryption. Remember that. + * + * @throws Throwable + */ + @Test(expected = IllegalArgumentException.class) + public void http2() throws Throwable { + this.server = new ServerManager(false, true); + } + + + /** + * Test for HTTP/2 protocol encrypted by {@value ClientThread#ENCRYPTION_PROTOCOL}. + * + * @throws Throwable + */ + @Test + public void https2() throws Throwable { + final boolean secured = true; + final boolean useHttp2 = true; + this.server = new ServerManager(secured, useHttp2); + this.clients.addAll(createClients(secured, useHttp2)); + executeTest(); + } + + + private void executeTest() throws Throwable { + for (final ClientThread clientThread : clients) { + clientThread.start(); + } + final long start = System.currentTimeMillis(); + while (error.get() == null && System.currentTimeMillis() < start + TIME_IN_MILLIS) { + Thread.yield(); + } + for (final ClientThread clientThread : clients) { + clientThread.stopClient(); + } + for (final ClientThread clientThread : clients) { + // cycles are fast, so we can afford this. + clientThread.join(100L); + } + if (error.get() != null) { + throw error.get(); + } + assertTrue("No requests processed.", counter.get() > 0); + } + + + private Collection createClients(final boolean secured, final boolean useHttp2) throws Exception { + final List list = new ArrayList<>(COUNT_OF_CLIENTS); + for (int i = 0; i < COUNT_OF_CLIENTS; i++) { + list.add(createClient(secured, useHttp2, i + 1)); + } + return list; + } + + + private ClientThread createClient(final boolean secured, final boolean useHttp2, final int id) throws Exception { + @SuppressWarnings("unchecked") + final Class clazz = (Class) Class.forName(CLIENT_IMPLEMENTATION); + final Constructor constructor = clazz.getConstructor(ClientThreadSettings.class, + AtomicInteger.class, AtomicReference.class); + assertNotNull("constructor for " + CLIENT_IMPLEMENTATION, constructor); + final ClientThreadSettings settings = new ClientThreadSettings(id, secured, useHttp2, + server.getApplicationServiceEndpoint()); + return constructor.newInstance(settings, counter, error); + } +} diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/application/TestedEndpoint.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/application/TestedEndpoint.java new file mode 100644 index 0000000000..0a195f1934 --- /dev/null +++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/application/TestedEndpoint.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021, 2022 Payara Foundation 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.grizzly2.httpserver.test.application; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("tested-endpoint") +public class TestedEndpoint { + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String getIt() { + return "Got it!"; + } +} diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/ClientThread.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/ClientThread.java new file mode 100644 index 0000000000..2b11fab267 --- /dev/null +++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/ClientThread.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2021 Payara Foundation 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.grizzly2.httpserver.test.tools; + +import java.net.URI; +import java.security.GeneralSecurityException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; + +/** + * Why this? To simulate parallel client access - several clients intensively sending requests + * and verifying responses. + * + * @author David Matejcek + */ +public abstract class ClientThread extends Thread { + + /** Encryption used if you enable secured communication */ + public static final String ENCRYPTION_PROTOCOL = "TLSv1.2"; + + private final ClientThreadSettings settings; + private final AtomicInteger counter; + private final AtomicReference error; + private volatile boolean stop; + + + + public ClientThread(final ClientThreadSettings settings, final AtomicInteger counter, + final AtomicReference error) throws Exception { + this.settings = settings; + this.counter = counter; + this.error = error; + setName("client-" + settings.id); + setUncaughtExceptionHandler((t, e) -> { + stop = true; + error.compareAndSet(null, e); + }); + } + + + /** + * @return {@link ClientThreadSettings} + */ + protected final ClientThreadSettings getSettings() { + return this.settings; + } + + + /** + * Executes the requests and checks the response. + * + * @throws Throwable + */ + protected abstract void doGetAndCheckResponse() throws Throwable; + + + /** + * If the client is stateful, override this method. + */ + protected void disconnect() { + // by default nothing to do. + } + + + /** + * Instructs the client thread to stop when possible. + */ + public void stopClient() { + this.stop = true; + } + + + @Override + public final void run() { + try { + // stop when asked to stop or any "brother" thread observed throwable + while (!stop && error.get() == null) { + doGetAndCheckResponse(); + counter.incrementAndGet(); + } + } catch (final Throwable t) { + throw new IllegalStateException("The client thread failed: " + getName(), t); + } finally { + disconnect(); + } + } + + + /** + * @return Trivial {@link SSLContext}, accepting all certificates and host names + * @throws GeneralSecurityException + */ + protected static SSLContext createSslContext() throws GeneralSecurityException { + final SSLContext ctx = SSLContext.getInstance(ENCRYPTION_PROTOCOL); + ctx.init(null, new TrustManager[] {new NaiveTrustManager()}, null); + return ctx; + } + + /** + * Simplified configuration of the client thread. + */ + public static class ClientThreadSettings { + + /** Id of the client thread */ + public final int id; + /** True if the connection will be encrypted */ + public final boolean secured; + /** True if the protocol should be HTTP/2, false for HTTP 1.1 */ + public final boolean useHttp2; + /** The endpoint {@link URI} of the servlet */ + public final URI targetUri; + + /** + * Creates simplified configuration of the client thread. + * + * @param id id of the client thread + * @param secured true if the connection will be encrypted + * @param useHttp2 true if the protocol should be HTTP/2, false for HTTP 1.1 + * @param targetUri the endpoint {@link URI} of the servlet + */ + public ClientThreadSettings(final int id, final boolean secured, final boolean useHttp2, final URI targetUri) { + this.id = id; + this.secured = secured; + this.useHttp2 = useHttp2; + this.targetUri = targetUri; + } + } +} diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JdkHttpClientThread.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JdkHttpClientThread.java new file mode 100644 index 0000000000..8e9d5434a7 --- /dev/null +++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JdkHttpClientThread.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2021 Payara Foundation 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.grizzly2.httpserver.test.tools; + +import java.net.http.HttpClient; +import java.net.http.HttpClient.Version; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.glassfish.grizzly.http.util.Header; + +import jakarta.ws.rs.core.MediaType; + +import static org.junit.Assert.assertEquals; + +/** + * JDK11+ has it's own {@link HttpClient} implementation supporting both HTTP 1.1 and HTTP/2. + * + * @author David Matejcek + */ +public class JdkHttpClientThread extends ClientThread { + private final HttpClient client; + + public JdkHttpClientThread(final ClientThreadSettings settings, + final AtomicInteger counter, final AtomicReference error) throws Exception { + super(settings, counter, error); + this.client = createClient(settings.secured, settings.useHttp2); + } + + + @Override + public void doGetAndCheckResponse() throws Throwable { + final HttpRequest request = HttpRequest.newBuilder(getSettings().targetUri) + .header(Header.ContentType.toString(), MediaType.TEXT_PLAIN).GET().build(); + final HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, response.statusCode()); + assertEquals("Got it!", response.body()); + } + + + private static HttpClient createClient(final boolean secured, final boolean useHttp2) throws Exception { + final HttpClient.Builder builder = HttpClient.newBuilder() + .version(useHttp2 ? Version.HTTP_2 : Version.HTTP_1_1); + if (secured) { + builder.sslContext(createSslContext()); + } + return builder.build(); + } +} diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JerseyHttpClientThread.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JerseyHttpClientThread.java new file mode 100644 index 0000000000..dcf359f42a --- /dev/null +++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JerseyHttpClientThread.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021 Payara Foundation 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.grizzly2.httpserver.test.tools; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.glassfish.jersey.client.ClientConfig; + +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Invocation.Builder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Response; + +import static org.junit.Assert.assertEquals; + +/** + * Jersey doesn't support HTTP/2 at this moment, but this class may be extended later. + * Even this way it passes all tests, because server is still able to use HTTP 1.1 despite we + * configured it to use HTTP/2 + * + * @author David Matejcek + */ +public class JerseyHttpClientThread extends ClientThread { + + private final Client client; + + public JerseyHttpClientThread(final ClientThreadSettings settings, + final AtomicInteger counter, final AtomicReference error) throws Exception { + super(settings, counter, error); + this.client = createClient(settings.secured, settings.useHttp2); + } + + + @Override + public void doGetAndCheckResponse() throws Throwable { + final WebTarget path = client.target(getSettings().targetUri.toString()); + final Builder builder = path.request(); + final Response response = builder.get(); + final String responseMsg = response.readEntity(String.class); + assertEquals(200, response.getStatus()); + assertEquals("Got it!", responseMsg); + } + + + private static Client createClient(final boolean secured, final boolean useHttp2) throws Exception { + final ClientBuilder builder = ClientBuilder.newBuilder().withConfig(new ClientConfig()); + if (secured) { + builder.sslContext(createSslContext()); + } + return builder.build(); + } +} diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JettyHttpClientThread.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JettyHttpClientThread.java new file mode 100644 index 0000000000..171d534fce --- /dev/null +++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JettyHttpClientThread.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2021 Payara Foundation 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.grizzly2.httpserver.test.tools; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpClientTransport; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; +import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; +import org.eclipse.jetty.io.ClientConnector; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import static org.junit.Assert.assertEquals; + +/** + * Jetty {@link HttpClient} supports both HTTP 1.1 and HTTP/2. + * + * @author David Matejcek + */ +public class JettyHttpClientThread extends ClientThread { + + private final HttpClient client; + + public JettyHttpClientThread(final ClientThreadSettings settings, final AtomicInteger counter, + final AtomicReference error) throws Exception { + super(settings, counter, error); + this.client = createJettyClient(settings.secured, settings.useHttp2); + } + + + @Override + protected void disconnect() { + try { + this.client.stop(); + } catch (Exception e) { + throw new IllegalStateException("Could not stop the client: " + getName(), e); + } + } + + @Override + public void doGetAndCheckResponse() throws Throwable { + final ContentResponse response = this.client.GET(getSettings().targetUri); + assertEquals(200, response.getStatus()); + assertEquals("Got it!", response.getContentAsString()); + } + + + private static HttpClient createJettyClient(final boolean secured, final boolean useHttp2) throws Exception { + if (!secured && !useHttp2) { + final HttpClient httpClient = new HttpClient(); + httpClient.start(); + return httpClient; + } + + final SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(); + sslContextFactory.setSslContext(createSslContext()); + final ClientConnector connector = new ClientConnector(); + connector.setSslContextFactory(sslContextFactory); + + final HttpClientTransport transport; + if (useHttp2) { + transport = new HttpClientTransportOverHTTP2(new HTTP2Client(connector)); + } else { + transport = new HttpClientTransportOverHTTP(connector); + } + + final HttpClient httpClient = new HttpClient(transport); + httpClient.start(); + return httpClient; + } +} diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/KeyStoreManager.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/KeyStoreManager.java new file mode 100644 index 0000000000..4cb8de3337 --- /dev/null +++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/KeyStoreManager.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2021 Payara Foundation 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.grizzly2.httpserver.test.tools; + +import java.io.ByteArrayOutputStream; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.Random; + +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; + +/** + * Creates a new keystore in memory. + * This keystore contains just a private key and a self-signed certificate valid for two days. + * + * @author David Matejcek + */ +public class KeyStoreManager { + + private static final String KEYSTORE_PASSWORD = ""; + private final KeyStore keyStore; + private final byte[] keyStoreBytes; + + /** + * @param hostname - hostname, used for CN value of the self-signed certificate + */ + public KeyStoreManager(final String hostname) { + try { + this.keyStore = KeyStore.getInstance("PKCS12"); + this.keyStore.load(null); + this.keyStoreBytes = generatePrivateKeyAndCertificate(hostname, this.keyStore); + } catch (Exception e) { + throw new IllegalStateException("Could not initialize the keystore.", e); + } + } + + + /** + * @return {@link KeyStore} + */ + public KeyStore getKeyStore() { + return this.keyStore; + } + + + /** + * @return the key store serialized to a byte array + */ + public byte[] getKeyStoreBytes() { + return this.keyStoreBytes; + } + + + /** + * @return the key store password + */ + public String getKeyStorePassword() { + return KEYSTORE_PASSWORD; + } + + + private static byte[] generatePrivateKeyAndCertificate(final String hostname, final KeyStore keyStore) { + try { + final KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); + generator.initialize(2048, SecureRandom.getInstance("SHA1PRNG")); + final KeyPair keyPair = generator.generateKeyPair(); + final PrivateKey privateKey = keyPair.getPrivate(); + final PublicKey publicKey = keyPair.getPublic(); + + final BigInteger serial = new BigInteger(256, new Random(System.currentTimeMillis())); + final Instant validFrom = Instant.now().minusSeconds(60L); + final Instant validTo = validFrom.plus(2, ChronoUnit.DAYS); + + final ASN1Sequence pubSeq = ASN1Sequence.getInstance(publicKey.getEncoded()); + final SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(pubSeq.getEncoded()); + final X500Name name = new X500Name( + "CN=" + hostname + ", OU=Jersey Container, O=Eclipse Foundation, L=Brussels, ST=Belgium, C=BE"); + final X509v3CertificateBuilder builder = new X509v3CertificateBuilder( + name, serial, Date.from(validFrom), Date.from(validTo), name, info); + final JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder("SHA512withRSA"); + final ContentSigner signer = signerBuilder.build(privateKey); + + final X509CertificateHolder cHolder = builder.build(signer); + final X509Certificate certificate = new JcaX509CertificateConverter().getCertificate(cHolder); + + keyStore.setKeyEntry(hostname, privateKey, KEYSTORE_PASSWORD.toCharArray(), + new Certificate[] {certificate}); + return toBytes(keyStore); + } catch (final Exception e) { + throw new IllegalStateException("Could not initialize the keystore", e); + } + } + + + private static byte[] toBytes(final KeyStore keyStore) throws Exception { + try (ByteArrayOutputStream os = new ByteArrayOutputStream(1024)) { + keyStore.store(os, new char[0]); + return os.toByteArray(); + } + } +} diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/NaiveTrustManager.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/NaiveTrustManager.java new file mode 100644 index 0000000000..85e265d533 --- /dev/null +++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/NaiveTrustManager.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021 Payara Foundation 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.grizzly2.httpserver.test.tools; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.X509TrustManager; + + +/** + * This trust manager accepts all certificates. + *

+ * DO NOT USE ON PRODUCTION CODE!!! + * + * @author David Matejcek + */ +public class NaiveTrustManager implements X509TrustManager { + @Override + public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { + // Everyone is trusted! + } + + @Override + public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { + // Everyone is trusted! + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } +} diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/ServerManager.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/ServerManager.java new file mode 100644 index 0000000000..225e02d2f1 --- /dev/null +++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/ServerManager.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2021 Payara Foundation 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.grizzly2.httpserver.test.tools; + +import java.io.Closeable; +import java.io.IOException; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.ServerSocket; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.UnknownHostException; + +import org.glassfish.grizzly.http.server.HttpServer; +import org.glassfish.grizzly.http.server.NetworkListener; +import org.glassfish.grizzly.http2.Http2AddOn; +import org.glassfish.grizzly.http2.Http2Configuration; +import org.glassfish.grizzly.ssl.SSLContextConfigurator; +import org.glassfish.grizzly.ssl.SSLEngineConfigurator; +import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; +import org.glassfish.jersey.grizzly2.httpserver.test.application.TestedEndpoint; +import org.glassfish.jersey.server.ResourceConfig; + +/** + * This manager maintains the lifecycle of the {@link HttpServer} and trivial rest application. + * + * @author David Matejcek + */ +public class ServerManager implements Closeable { + + private static final String LISTENER_NAME_GRIZZLY = "grizzly"; + private static final String PROTOCOL_HTTPS = "https"; + private static final String PROTOCOL_HTTP = "http"; + private static final String APPLICATION_CONTEXT = "/test-application"; + private static final String SERVICE_CONTEXT = "/tested-endpoint"; + + private final URI endpointUri; + private final HttpServer server; + + + /** + * Initializes the server environment and starts the server. + * + * @param secured - set true to enable network encryption + * @param useHttp2 - set true to enable HTTP/2; then secured must be set to true too. + * @throws IOException + */ + public ServerManager(final boolean secured, final boolean useHttp2) throws IOException { + this.endpointUri = getEndpointUri(secured, APPLICATION_CONTEXT); + final NetworkListener listener = createListener(secured, useHttp2, endpointUri.getHost(), endpointUri.getPort()); + final ResourceConfig resourceConfig = createResourceConfig(); + this.server = startServer(listener, this.endpointUri, resourceConfig); + } + + + /** + * @return {@link URI} of the application endpoint. + */ + public URI getApplicationEndpoint() { + return this.endpointUri; + } + + + /** + * @return {@link URI} of the deployed service endpoint. + */ + public URI getApplicationServiceEndpoint() { + return URI.create(getApplicationEndpoint() + SERVICE_CONTEXT); + } + + + /** + * Calls the {@link HttpServer#shutdownNow()}. The server and all it's resources are destroyed. + */ + @Override + public void close() { + if (server != null) { + server.shutdownNow(); + } + } + + + private static URI getEndpointUri(final boolean secured, final String applicationContext) { + try { + final String protocol = secured ? PROTOCOL_HTTPS : PROTOCOL_HTTP; + return new URL(protocol, getLocalhost(), getFreePort(), applicationContext).toURI(); + } catch (MalformedURLException | URISyntaxException e) { + throw new IllegalStateException("Unable to create an endpoint URI.", e); + } + } + + + private static String getLocalhost() { + try { + return InetAddress.getLocalHost().getHostName(); + } catch (final UnknownHostException e) { + return "localhost"; + } + } + + + /** + * Tries to alocate a free local port. + * + * @return a free local port number. + * @throws IllegalStateException if it fails for 20 times + */ + private static int getFreePort() { + int attempts = 0; + while (true) { + attempts++; + try (ServerSocket socket = new ServerSocket(0)) { + final int port = socket.getLocalPort(); + socket.setSoTimeout(1); + socket.setReuseAddress(true); + return port; + } catch (final IOException e) { + if (attempts >= 20) { + throw new IllegalStateException("Cannot open random port, tried 20 times.", e); + } + } + } + } + + + private static NetworkListener createListener(final boolean secured, final boolean useHttp2, final String host, + final int port) { + if (useHttp2 && !secured) { + throw new IllegalArgumentException("HTTP/2 cannot be used without encryption"); + } + final NetworkListener listener = new NetworkListener(LISTENER_NAME_GRIZZLY, host, port); + listener.setSecure(secured); + if (secured) { + listener.setSSLEngineConfig(createSSLEngineConfigurator(host)); + } + if (useHttp2) { + listener.registerAddOn(createHttp2AddOn()); + } + return listener; + } + + + private static SSLEngineConfigurator createSSLEngineConfigurator(final String host) { + final KeyStoreManager keyStoreManager = new KeyStoreManager(host); + final SSLContextConfigurator configurator = new SSLContextConfigurator(); + configurator.setKeyStoreBytes(keyStoreManager.getKeyStoreBytes()); + configurator.setKeyStorePass(keyStoreManager.getKeyStorePassword()); + configurator.setTrustStoreBytes(keyStoreManager.getKeyStoreBytes()); + configurator.setTrustStorePass(keyStoreManager.getKeyStorePassword()); + final SSLEngineConfigurator sslEngineConfigurator = new SSLEngineConfigurator(configurator) + .setClientMode(false).setNeedClientAuth(false); + return sslEngineConfigurator; + } + + + private static Http2AddOn createHttp2AddOn() { + final Http2Configuration configuration = Http2Configuration.builder().build(); + return new Http2AddOn(configuration); + } + + + private static ResourceConfig createResourceConfig() { + return new ResourceConfig().registerClasses(TestedEndpoint.class); + } + + + private static HttpServer startServer(final NetworkListener listener, final URI endpointUri, + final ResourceConfig resourceConfig) { + final HttpServer srv = GrizzlyHttpServerFactory.createHttpServer(endpointUri, resourceConfig, false); + try { + srv.addListener(listener); + srv.start(); + return srv; + } catch (final IOException e) { + throw new IllegalStateException("Could not start the server!", e); + } + } +} diff --git a/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServerFactory.java b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServerFactory.java index 6b8b8c17e4..a136edfd24 100644 --- a/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServerFactory.java +++ b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022 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 diff --git a/core-common/src/main/java/org/glassfish/jersey/AbstractFeatureConfigurator.java b/core-common/src/main/java/org/glassfish/jersey/AbstractFeatureConfigurator.java index 307f3addc5..a73b185de7 100644 --- a/core-common/src/main/java/org/glassfish/jersey/AbstractFeatureConfigurator.java +++ b/core-common/src/main/java/org/glassfish/jersey/AbstractFeatureConfigurator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2022 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 diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/DynamicFeatureConfigurator.java b/core-common/src/main/java/org/glassfish/jersey/internal/DynamicFeatureConfigurator.java index 295e5e816b..12eb78bd77 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/DynamicFeatureConfigurator.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/DynamicFeatureConfigurator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2022 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 diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/FeatureConfigurator.java b/core-common/src/main/java/org/glassfish/jersey/internal/FeatureConfigurator.java index 809dc03bf4..2b0acbd69c 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/FeatureConfigurator.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/FeatureConfigurator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2022 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 diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ServerRuntime.java b/core-server/src/main/java/org/glassfish/jersey/server/ServerRuntime.java index 9c945513cc..06e0f94561 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/ServerRuntime.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/ServerRuntime.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 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 diff --git a/examples/helloworld-weld/pom.xml b/examples/helloworld-weld/pom.xml index 16b3fb0e15..f26f9e5e68 100644 --- a/examples/helloworld-weld/pom.xml +++ b/examples/helloworld-weld/pom.xml @@ -1,7 +1,7 @@