DelegatedTaskExecutorTestCase.java

/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2021 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package io.undertow.server.ssl;


import io.undertow.Undertow;
import io.undertow.server.handlers.ResponseCodeHandler;
import io.undertow.testutils.DefaultServer;
import io.undertow.testutils.TestHttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.junit.Test;

import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

/**
 * @author Carter Kozak
 */
public class DelegatedTaskExecutorTestCase {

    @Test
    public void testDelegatedTaskExecutorIsUsed() throws Exception {
        ExecutorService delegatedTaskExecutor = Executors.newSingleThreadExecutor();
        AtomicInteger counter = new AtomicInteger();
        Undertow undertow = Undertow.builder()
                .addHttpsListener(0, null, DefaultServer.getServerSslContext())
                .setSslEngineDelegatedTaskExecutor(task -> {
                    counter.getAndIncrement();
                    delegatedTaskExecutor.execute(task);
                })
                .setHandler(ResponseCodeHandler.HANDLE_200)
                .build();

        TestHttpClient client = new TestHttpClient();
        client.setSSLContext(DefaultServer.getClientSSLContext());
        undertow.start();
        int port = port(undertow);
        try(CloseableHttpResponse response = client.execute(new HttpGet("https://localhost:" + port))) {
            assertEquals(200, response.getStatusLine().getStatusCode());
            assertTrue("expected interactions with the delegated task executor", counter.get() > 0);
        } finally {
            undertow.stop();
            client.getConnectionManager().shutdown();
            List<Runnable> tasks = delegatedTaskExecutor.shutdownNow();
            for (Runnable task: tasks) {
                task.run();
            }
            assertTrue(
                    "ExecutorService did not shut down in time",
                    delegatedTaskExecutor.awaitTermination(1, TimeUnit.SECONDS));
        }
    }

    @Test
    public void testRejection() {
        Undertow undertow = Undertow.builder()
                .addHttpsListener(0, null, DefaultServer.getServerSslContext())
                .setSslEngineDelegatedTaskExecutor(ignoredTask -> {
                    throw new RejectedExecutionException();
                })
                .setHandler(ResponseCodeHandler.HANDLE_200)
                .build();

        TestHttpClient client = new TestHttpClient();
        client.setSSLContext(DefaultServer.getClientSSLContext());
        undertow.start();
        try {
            int port = port(undertow);
            HttpGet request = new HttpGet("https://localhost:" + port);
            try {
                client.execute(request);
                fail("Expected an exception");
            } catch (SSLHandshakeException handshakeException) {
                // expected one of:
                // - Remote host closed connection during handshake
                // - Remote host terminated the handshake
                // This exception comes from the jvm and may change in future
                // releases so we don't verify an exact match.
                String message = handshakeException.getMessage();
                System.out.println(message);
                assertTrue(
                        "message was: " + message,
                        message != null && (message.contains("closed") || message.contains("terminated")));
            } catch (IOException e) {
                throw new AssertionError(e);
            }
        } finally {
            undertow.stop();
            client.getConnectionManager().shutdown();
            // sleep 1 s to prevent BindException (Address already in use) when running the CI
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ignore) {}
        }
    }

    private static int port(Undertow undertow) {
        if (undertow.getListenerInfo().size() != 1) {
            throw new IllegalStateException("Expected exactly one listener");
        }
        InetSocketAddress address = (InetSocketAddress) undertow.getListenerInfo().get(0).getAddress();
        return address.getPort();
    }
}