merge release history into 3.x
diff --git a/containers/grizzly2-http/pom.xml b/containers/grizzly2-http/pom.xml
index 49e61f9..4969da9 100644
--- a/containers/grizzly2-http/pom.xml
+++ b/containers/grizzly2-http/pom.xml
@@ -17,7 +17,8 @@
-->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
@@ -32,6 +33,14 @@
<description>Grizzly 2 Http Container.</description>
+ <properties>
+ <!-- alternatives: JerseyHttpClientThread, JettyClientThread in the same package -->
+ <client>JdkHttpClientThread</client>
+ <clientImplPackage>org.glassfish.jersey.grizzly2.httpserver.test.tools</clientImplPackage>
+ <clientImpl>${clientImplPackage}.${client}</clientImpl>
+ <testMemory>-Xms160m -Xmx160m -Xss512k</testMemory>
+ </properties>
+
<dependencies>
<dependency>
<groupId>jakarta.inject</groupId>
@@ -41,6 +50,40 @@
<groupId>org.glassfish.grizzly</groupId>
<artifactId>grizzly-http-server</artifactId>
</dependency>
+
+ <dependency>
+ <groupId>org.glassfish.grizzly</groupId>
+ <artifactId>grizzly-http2</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.grizzly</groupId>
+ <artifactId>grizzly-npn-api</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-client</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.http2</groupId>
+ <artifactId>http2-http-client-transport</artifactId>
+ <version>${jetty.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcmail-jdk15on</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
@@ -60,7 +103,87 @@
<artifactId>maven-bundle-plugin</artifactId>
<inherited>true</inherited>
</plugin>
+
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <!--
+ memory settings:
+ - to avoid unstable JVM process; fail fast, don't produce dump
+ - memory sizes must reflect count of clients!
+ test time:
+ - must not affect stability of the server; if it does, the test detected a bug
+ - 10 seconds should be enough to process thousands of requests, if there would
+ be some leak, it should be visible
+ client count:
+ - each client is reusable to produce parallel requests, but each also consumes
+ significiant amount of memory, in this case shared with the server.
+ -->
+ <argLine>
+ ${testMemory} -XX:+CrashOnOutOfMemoryError -XX:-HeapDumpOnOutOfMemoryError
+ -DtestTime=10 -DclientCount=30
+ -DclientImpl=${clientImpl}
+ </argLine>
+ <trimStackTrace>false</trimStackTrace>
+ <parallel>methods</parallel>
+ <threadCountMethods>3</threadCountMethods>
+ </configuration>
+ </plugin>
</plugins>
</build>
+ <profiles>
+ <profile>
+ <id>jdk11</id>
+ <activation>
+ <jdk>[11,)</jdk>
+ </activation>
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>default-testCompile</id>
+ <configuration>
+ <source>11</source>
+ <target>11</target>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+ </profile>
+ <profile>
+ <id>jdk8</id>
+ <activation>
+ <jdk>1.8</jdk>
+ </activation>
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>default-testCompile</id>
+ <configuration>
+ <!--
+ Jetty client is not compatible with JDK8, older versions need different setup
+ JDK HTTP client is not in JDK8 at all
+ Jersey Client doesn't support HTTP/2 (at least not directly)
+ -->
+ <skip>true</skip>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+ </profile>
+ </profiles>
</project>
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 0000000..5fb5e08
--- /dev/null
+++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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;
+
+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.
+ * <p>
+ * 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<ClientThread> clients = new ArrayList<>(COUNT_OF_CLIENTS);
+ private AtomicReference<Throwable> 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<ClientThread> createClients(final boolean secured, final boolean useHttp2) throws Exception {
+ final List<ClientThread> 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<ClientThread> clazz = (Class<ClientThread>) Class.forName(CLIENT_IMPLEMENTATION);
+ final Constructor<ClientThread> 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 0000000..3ad7fb8
--- /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 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 0000000..2b11fab
--- /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<Throwable> error;
+ private volatile boolean stop;
+
+
+
+ public ClientThread(final ClientThreadSettings settings, final AtomicInteger counter,
+ final AtomicReference<Throwable> 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 0000000..8e9d543
--- /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<Throwable> 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<String> 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 0000000..dcf359f
--- /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<Throwable> 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 0000000..171d534
--- /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<Throwable> 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 0000000..4cb8de3
--- /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 0000000..85e265d
--- /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.
+ * <p>
+ * <b>DO NOT USE ON PRODUCTION CODE!!!</b>
+ *
+ * @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 0000000..225e02d
--- /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/examples/helloworld-weld/pom.xml b/examples/helloworld-weld/pom.xml
index 16b3fb0..4ad42c1 100644
--- a/examples/helloworld-weld/pom.xml
+++ b/examples/helloworld-weld/pom.xml
@@ -38,7 +38,6 @@
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
- <version>${cdi.api.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
diff --git a/pom.xml b/pom.xml
index eab3679..fd03161 100644
--- a/pom.xml
+++ b/pom.xml
@@ -213,7 +213,7 @@
</license>
</licenses>
- <scm>
+ <scm>
<connection>scm:git:git@github.com:jersey/jersey.git</connection>
<developerConnection>scm:git:git@github.com:eclipse-ee4j/jersey.git</developerConnection>
<url>https://github.com/eclipse-ee4j/jersey</url>
@@ -1595,6 +1595,11 @@
</dependency>
<dependency>
<groupId>org.glassfish.grizzly</groupId>
+ <artifactId>grizzly-http2</artifactId>
+ <version>${grizzly2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.grizzly</groupId>
<artifactId>grizzly-http-servlet</artifactId>
<version>${grizzly2.version}</version>
</dependency>
@@ -1613,6 +1618,11 @@
<artifactId>grizzly-http-client</artifactId>
<version>${grizzly.client.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.glassfish.grizzly</groupId>
+ <artifactId>grizzly-npn-api</artifactId>
+ <version>${grizzly.npn.version}</version>
+ </dependency>
<dependency>
<groupId>io.netty</groupId>
@@ -1974,6 +1984,18 @@
<version>${xmlunit.version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ <version>${bouncycastle.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcmail-jdk15on</artifactId>
+ <version>${bouncycastle.version}</version>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.apache.felix</groupId>
@@ -2057,6 +2079,7 @@
<asm.version>9.2</asm.version>
<bnd.plugin.version>2.3.6</bnd.plugin.version>
+ <bouncycastle.version>1.68</bouncycastle.version>
<commons-lang3.version>3.3.2</commons-lang3.version>
<microprofile.config.version>2.0</microprofile.config.version>
<checkstyle.mvn.plugin.version>3.1.0</checkstyle.mvn.plugin.version>
@@ -2127,6 +2150,7 @@
<cdi.api.version>3.0.0</cdi.api.version>
<ejb.version>4.0.0</ejb.version>
<grizzly2.version>3.0.0</grizzly2.version>
+ <grizzly.npn.version>2.0.0</grizzly.npn.version>
<hk2.version>3.0.1</hk2.version>
<jsp.version>3.0.0</jsp.version>
<jstl.version>2.0.0</jstl.version>
diff --git a/tests/pom.xml b/tests/pom.xml
index 259a689..ca0cccf 100644
--- a/tests/pom.xml
+++ b/tests/pom.xml
@@ -68,7 +68,6 @@
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
- <version>${project.version}</version>
</dependency>
</dependencies>