You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2022/04/02 12:59:23 UTC

[httpcomponents-core] branch master updated: Corrected TLS upgrade support in HttpAsyncRequester

This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git


The following commit(s) were added to refs/heads/master by this push:
     new 7103d55  Corrected TLS upgrade support in HttpAsyncRequester
7103d55 is described below

commit 7103d55bc433b5e68a8eb5b95afd770924d716f2
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sat Apr 2 14:57:02 2022 +0200

    Corrected TLS upgrade support in HttpAsyncRequester
---
 .../hc/core5/testing/nio/TLSUpgradeTest.java       | 197 +++++++++++++++++++++
 .../impl/bootstrap/AsyncRequesterBootstrap.java    |   7 +-
 2 files changed, 202 insertions(+), 2 deletions(-)

diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/TLSUpgradeTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/TLSUpgradeTest.java
new file mode 100644
index 0000000..242218f
--- /dev/null
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/TLSUpgradeTest.java
@@ -0,0 +1,197 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.testing.nio;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.Future;
+
+import org.apache.hc.core5.concurrent.BasicFuture;
+import org.apache.hc.core5.concurrent.FutureContribution;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.Message;
+import org.apache.hc.core5.http.Method;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http.impl.bootstrap.AsyncRequesterBootstrap;
+import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap;
+import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester;
+import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
+import org.apache.hc.core5.http.nio.AsyncClientEndpoint;
+import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
+import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer;
+import org.apache.hc.core5.http.nio.ssl.BasicClientTlsStrategy;
+import org.apache.hc.core5.http.nio.ssl.BasicServerTlsStrategy;
+import org.apache.hc.core5.http.nio.ssl.TlsUpgradeCapable;
+import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
+import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
+import org.apache.hc.core5.http.protocol.UriPatternMatcher;
+import org.apache.hc.core5.io.CloseMode;
+import org.apache.hc.core5.reactor.IOReactorConfig;
+import org.apache.hc.core5.reactor.ListenerEndpoint;
+import org.apache.hc.core5.reactor.ProtocolIOSession;
+import org.apache.hc.core5.reactor.ssl.TlsDetails;
+import org.apache.hc.core5.testing.SSLTestContexts;
+import org.apache.hc.core5.testing.classic.LoggingConnPoolListener;
+import org.apache.hc.core5.util.Timeout;
+import org.hamcrest.CoreMatchers;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.rules.ExternalResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TLSUpgradeTest {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final Timeout TIMEOUT = Timeout.ofSeconds(30);
+
+    private HttpAsyncServer server;
+
+    @Rule
+    public ExternalResource serverResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            log.debug("Starting up test server");
+            server = AsyncServerBootstrap.bootstrap()
+                    .setLookupRegistry(new UriPatternMatcher<>())
+                    .setIOReactorConfig(
+                            IOReactorConfig.custom()
+                                    .setSoTimeout(TIMEOUT)
+                                    .build())
+                    .register("*", () -> new EchoHandler(2048))
+                    .setTlsStrategy(new BasicServerTlsStrategy(SSLTestContexts.createServerSSLContext()))
+                    .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER)
+                    .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
+                    .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
+                    .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
+                    .create();
+        }
+
+        @Override
+        protected void after() {
+            log.debug("Shutting down test server");
+            if (server != null) {
+                server.close(CloseMode.GRACEFUL);
+            }
+        }
+
+    };
+
+    private HttpAsyncRequester requester;
+
+    @Rule
+    public ExternalResource clientResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            log.debug("Starting up test client");
+            requester = AsyncRequesterBootstrap.bootstrap()
+                    .setIOReactorConfig(IOReactorConfig.custom()
+                            .setSoTimeout(TIMEOUT)
+                            .build())
+                    .setTlsStrategy(new BasicClientTlsStrategy(SSLTestContexts.createClientSSLContext()))
+                    .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT)
+                    .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
+                    .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
+                    .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
+                    .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
+                    .create();
+        }
+
+        @Override
+        protected void after() {
+            log.debug("Shutting down test client");
+            if (requester != null) {
+                requester.close(CloseMode.GRACEFUL);
+            }
+        }
+
+    };
+
+    @Test
+    public void testTLSUpgrade() throws Exception {
+        server.start();
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS);
+        final ListenerEndpoint listener = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+        final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
+        requester.start();
+
+        final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort());
+        final Future<Message<HttpResponse, String>> resultFuture1 = requester.execute(
+                new BasicRequestProducer(Method.POST, target, "/stuff",
+                        new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
+                new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null);
+        final Message<HttpResponse, String> message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+        assertThat(message1, CoreMatchers.notNullValue());
+        final HttpResponse response1 = message1.getHead();
+        assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
+        final String body1 = message1.getBody();
+        assertThat(body1, CoreMatchers.equalTo("some stuff"));
+
+        // Connect using plain HTTP scheme
+        final Future<AsyncClientEndpoint> endpointFuture = requester.connect(
+                new HttpHost(URIScheme.HTTP.id, "localhost", address.getPort()), TIMEOUT);
+
+        final AsyncClientEndpoint clientEndpoint = endpointFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+        Assertions.assertInstanceOf(TlsUpgradeCapable.class, clientEndpoint);
+
+        // Upgrade to TLS
+        final BasicFuture<TlsDetails> tlsFuture = new BasicFuture<>(null);
+        ((TlsUpgradeCapable) clientEndpoint).tlsUpgrade(target, new FutureContribution<ProtocolIOSession>(tlsFuture) {
+
+            @Override
+            public void completed(final ProtocolIOSession protocolIOSession) {
+                tlsFuture.completed(protocolIOSession.getTlsDetails());
+            }
+
+        });
+
+        final TlsDetails tlsDetails = tlsFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+        Assertions.assertNotNull(tlsDetails);
+
+        // Execute request over HTTPS
+        final Future<Message<HttpResponse, String>> resultFuture2 = clientEndpoint.execute(
+                new BasicRequestProducer(Method.POST, target, "/stuff",
+                        new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
+                new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
+        final Message<HttpResponse, String> message2 = resultFuture2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+        assertThat(message2, CoreMatchers.notNullValue());
+        final HttpResponse response2 = message2.getHead();
+        assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
+        final String body2 = message2.getBody();
+        assertThat(body2, CoreMatchers.equalTo("some stuff"));
+    }
+
+}
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequesterBootstrap.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequesterBootstrap.java
index 44810d9..879adea 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequesterBootstrap.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequesterBootstrap.java
@@ -240,9 +240,10 @@ public class AsyncRequesterBootstrap {
                 null,
                 null,
                 streamListener);
+        final TlsStrategy tlsStrategyCopy = tlsStrategy != null ? tlsStrategy : new BasicClientTlsStrategy();
         final IOEventHandlerFactory ioEventHandlerFactory = new ClientHttp1IOEventHandlerFactory(
                 streamDuplexerFactory,
-                tlsStrategy != null ? tlsStrategy : new BasicClientTlsStrategy(),
+                tlsStrategyCopy,
                 handshakeTimeout);
         return new HttpAsyncRequester(
                 ioReactorConfig,
@@ -250,7 +251,9 @@ public class AsyncRequesterBootstrap {
                 ioSessionDecorator,
                 exceptionCallback,
                 sessionListener,
-                connPool);
+                connPool,
+                tlsStrategyCopy,
+                handshakeTimeout);
     }
 
 }