GrizzlyHttpServerTest.java

/*
 * Copyright (c) 2021, 2022 Oracle and/or its affiliates. All rights reserved.
 * 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.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.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;

    @BeforeAll
    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");
    }


    @BeforeEach
    public void init() {
        this.counter = new AtomicInteger();
    }


    @AfterEach
    public void cleanup(TestInfo testInfo) {
        error = null;
        System.out.println(String.format("Server processed %s requests of test %s.", counter, testInfo.getDisplayName()));
        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
    public void http2() {
        Assertions.assertThrows(IllegalArgumentException.class,
                () ->
        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(counter.get() > 0, "No requests processed.");
    }


    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, "constructor for " + CLIENT_IMPLEMENTATION);
        final ClientThreadSettings settings = new ClientThreadSettings(id, secured, useHttp2,
            server.getApplicationServiceEndpoint());
        return constructor.newInstance(settings, counter, error);
    }
}