Skip to content

Commit

Permalink
GrizzlyHttpServerTest - testing stability with HTTP, HTTPS and HTTP/2
Browse files Browse the repository at this point in the history
- parallel execution on random free ports
- disabled for jdk8
- possibility to use three different clients
  - jersey-client - but it doesn't support HTTP/2
  - jdk11+ HttpClient - but it is not available in older JDK versions
  - jetty-client - but version compatible with jdk8 uses different classes
      than version compatible with jdk11
- each test creates a configured server, then creates several clients
  which are sending GET requests for given time.
- all responses must be HTTP 200
- after the test then number of processed requests is written to the STDOUT
- if there will be just one non compliant response, all clients are stopped
  and the test fails.
- originally created to reproduce the issue eclipse-ee4j#2125 of the Grizzly project,
  which used Jersey and Grizzly together.

Signed-off-by: David Matějček <dmatej@seznam.cz>
  • Loading branch information
dmatej committed Sep 8, 2021
1 parent 5fc527c commit 883db8a
Show file tree
Hide file tree
Showing 13 changed files with 1,104 additions and 4 deletions.
125 changes: 124 additions & 1 deletion containers/grizzly2-http/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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>
Expand All @@ -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>
Expand All @@ -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>
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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!";
}
}
Loading

0 comments on commit 883db8a

Please sign in to comment.