BasicHttpsTest.java

/*
 * Copyright 2010 Ning, Inc.
 *
 * This program is licensed to you 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 org.asynchttpclient;

import io.github.artsok.RepeatedIfExceptionsTest;
import jakarta.servlet.http.HttpServletResponse;
import org.asynchttpclient.channel.KeepAliveStrategy;
import org.asynchttpclient.test.EventCollectingHandler;
import org.asynchttpclient.testserver.HttpServer;
import org.asynchttpclient.testserver.HttpTest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Timeout;

import javax.net.ssl.SSLHandshakeException;
import java.time.Duration;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.asynchttpclient.Dsl.config;
import static org.asynchttpclient.test.TestUtils.LARGE_IMAGE_FILE;
import static org.asynchttpclient.test.TestUtils.SIMPLE_TEXT_FILE;
import static org.asynchttpclient.test.TestUtils.SIMPLE_TEXT_FILE_STRING;
import static org.asynchttpclient.test.TestUtils.TIMEOUT;
import static org.asynchttpclient.test.TestUtils.createSslEngineFactory;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class BasicHttpsTest extends HttpTest {

    private HttpServer server;

    @BeforeEach
    public void start() throws Throwable {
        server = new HttpServer();
        server.start();
    }

    @AfterEach
    public void stop() throws Throwable {
        server.close();
    }

    private String getTargetUrl() {
        return server.getHttpsUrl() + "/foo/bar";
    }

    @RepeatedIfExceptionsTest(repeats = 5)
    public void postFileOverHttps() throws Throwable {
        logger.debug(">>> postBodyOverHttps");
        withClient(config().setSslEngineFactory(createSslEngineFactory())).run(client ->
                withServer(server).run(server -> {
                    server.enqueueEcho();

                    Response resp = client.preparePost(getTargetUrl()).setBody(SIMPLE_TEXT_FILE).setHeader(CONTENT_TYPE, "text/html").execute().get();
                    assertNotNull(resp);
                    assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK);
                    assertEquals(resp.getResponseBody(), SIMPLE_TEXT_FILE_STRING);
                }));
        logger.debug("<<< postBodyOverHttps");
    }

    @RepeatedIfExceptionsTest(repeats = 5)
    public void postLargeFileOverHttps() throws Throwable {
        logger.debug(">>> postLargeFileOverHttps");
        withClient(config().setSslEngineFactory(createSslEngineFactory())).run(client ->
                withServer(server).run(server -> {
                    server.enqueueEcho();

                    Response resp = client.preparePost(getTargetUrl()).setBody(LARGE_IMAGE_FILE).setHeader(CONTENT_TYPE, "image/png").execute().get();
                    assertNotNull(resp);
                    assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK);
                    assertEquals(resp.getResponseBodyAsBytes().length, LARGE_IMAGE_FILE.length());
                }));
        logger.debug("<<< postLargeFileOverHttps");
    }

    @RepeatedIfExceptionsTest(repeats = 5)
    public void multipleSequentialPostRequestsOverHttps() throws Throwable {
        logger.debug(">>> multipleSequentialPostRequestsOverHttps");
        withClient(config().setSslEngineFactory(createSslEngineFactory())).run(client ->
                withServer(server).run(server -> {
                    server.enqueueEcho();
                    server.enqueueEcho();

                    String body = "hello there";
                    Response response = client.preparePost(getTargetUrl()).setBody(body).setHeader(CONTENT_TYPE, "text/html").execute().get(TIMEOUT, SECONDS);
                    assertEquals(response.getResponseBody(), body);

                    response = client.preparePost(getTargetUrl()).setBody(body).setHeader(CONTENT_TYPE, "text/html").execute().get(TIMEOUT, SECONDS);
                    assertEquals(response.getResponseBody(), body);
                }));
        logger.debug("<<< multipleSequentialPostRequestsOverHttps");
    }

    @RepeatedIfExceptionsTest(repeats = 5)
    public void multipleConcurrentPostRequestsOverHttpsWithDisabledKeepAliveStrategy() throws Throwable {
        logger.debug(">>> multipleConcurrentPostRequestsOverHttpsWithDisabledKeepAliveStrategy");

        KeepAliveStrategy keepAliveStrategy = (remoteAddress, ahcRequest, nettyRequest, nettyResponse) -> !ahcRequest.getUri().isSecured();

        withClient(config().setSslEngineFactory(createSslEngineFactory()).setKeepAliveStrategy(keepAliveStrategy)).run(client ->
                withServer(server).run(server -> {
                    server.enqueueEcho();
                    server.enqueueEcho();
                    server.enqueueEcho();

                    String body = "hello there";

                    client.preparePost(getTargetUrl()).setBody(body).setHeader(CONTENT_TYPE, "text/html").execute();
                    client.preparePost(getTargetUrl()).setBody(body).setHeader(CONTENT_TYPE, "text/html").execute();

                    Response response = client.preparePost(getTargetUrl()).setBody(body).setHeader(CONTENT_TYPE, "text/html").execute().get();
                    assertEquals(response.getResponseBody(), body);
                }));

        logger.debug("<<< multipleConcurrentPostRequestsOverHttpsWithDisabledKeepAliveStrategy");
    }

    @RepeatedIfExceptionsTest(repeats = 5)
    public void reconnectAfterFailedCertificationPath() throws Throwable {
        logger.debug(">>> reconnectAfterFailedCertificationPath");

        AtomicBoolean trust = new AtomicBoolean();

        withClient(config().setMaxRequestRetry(0).setSslEngineFactory(createSslEngineFactory(trust))).run(client ->
                withServer(server).run(server -> {
                    server.enqueueEcho();
                    server.enqueueEcho();

                    String body = "hello there";

                    // first request fails because server certificate is rejected
                    Throwable cause = null;
                    try {
                        client.preparePost(getTargetUrl()).setBody(body).setHeader(CONTENT_TYPE, "text/html").execute().get(TIMEOUT, SECONDS);
                    } catch (final ExecutionException e) {
                        cause = e.getCause();
                    }
                    assertNotNull(cause);

                    // second request should succeed
                    trust.set(true);
                    Response response = client.preparePost(getTargetUrl()).setBody(body).setHeader(CONTENT_TYPE, "text/html").execute().get(TIMEOUT, SECONDS);

                    assertEquals(response.getResponseBody(), body);
                }));
        logger.debug("<<< reconnectAfterFailedCertificationPath");
    }

    @RepeatedIfExceptionsTest(repeats = 5)
    @Timeout(unit = TimeUnit.MILLISECONDS, value = 2000)
    public void failInstantlyIfNotAllowedSelfSignedCertificate() throws Throwable {
        logger.debug(">>> failInstantlyIfNotAllowedSelfSignedCertificate");

        assertThrows(SSLHandshakeException.class, () -> {
            withClient(config().setMaxRequestRetry(0).setRequestTimeout(Duration.ofSeconds(2))).run(client ->
                    withServer(server).run(server -> {
                        try {
                            client.prepareGet(getTargetUrl()).execute().get(TIMEOUT, SECONDS);
                        } catch (ExecutionException e) {
                            throw e.getCause().getCause();
                        }
                    }));
        });
        logger.debug("<<< failInstantlyIfNotAllowedSelfSignedCertificate");
    }

    @RepeatedIfExceptionsTest(repeats = 5)
    public void testNormalEventsFired() throws Throwable {
        logger.debug(">>> testNormalEventsFired");

        withClient(config().setSslEngineFactory(createSslEngineFactory())).run(client ->
                withServer(server).run(server -> {
                    EventCollectingHandler handler = new EventCollectingHandler();

                    server.enqueueEcho();
                    client.preparePost(getTargetUrl()).setBody("whatever").execute(handler).get(3, SECONDS);
                    handler.waitForCompletion(3, SECONDS);

                    Object[] expectedEvents = {
                            CONNECTION_POOL_EVENT,
                            HOSTNAME_RESOLUTION_EVENT,
                            HOSTNAME_RESOLUTION_SUCCESS_EVENT,
                            CONNECTION_OPEN_EVENT,
                            CONNECTION_SUCCESS_EVENT,
                            TLS_HANDSHAKE_EVENT,
                            TLS_HANDSHAKE_SUCCESS_EVENT,
                            REQUEST_SEND_EVENT,
                            HEADERS_WRITTEN_EVENT,
                            STATUS_RECEIVED_EVENT,
                            HEADERS_RECEIVED_EVENT,
                            CONNECTION_OFFER_EVENT,
                            COMPLETED_EVENT};

                    assertArrayEquals(handler.firedEvents.toArray(), expectedEvents, "Got " + Arrays.toString(handler.firedEvents.toArray()));
                }));
        logger.debug("<<< testNormalEventsFired");
    }
}