hc-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ol...@apache.org
Subject [3/4] httpcomponents-core git commit: Strict / lax ALPN handshake mode for HTTP/2 multiplexing requester; ALPN handshake test
Date Tue, 14 Nov 2017 16:56:43 GMT
Strict / lax ALPN handshake mode for HTTP/2 multiplexing requester; ALPN handshake test


Project: http://git-wip-us.apache.org/repos/asf/httpcomponents-core/repo
Commit: http://git-wip-us.apache.org/repos/asf/httpcomponents-core/commit/001c9012
Tree: http://git-wip-us.apache.org/repos/asf/httpcomponents-core/tree/001c9012
Diff: http://git-wip-us.apache.org/repos/asf/httpcomponents-core/diff/001c9012

Branch: refs/heads/master
Commit: 001c9012eae6f62e15bd095c1a3c21e8f978a4f5
Parents: 190fbc3
Author: Oleg Kalnichevski <olegk@apache.org>
Authored: Tue Nov 14 15:30:21 2017 +0100
Committer: Oleg Kalnichevski <olegk@apache.org>
Committed: Tue Nov 14 16:33:13 2017 +0100

----------------------------------------------------------------------
 .../nio/Http2OnlyClientProtocolNegotiator.java  |  18 +-
 .../Http2MultiplexingRequesterBootstrap.java    |   9 +-
 .../hc/core5/testing/nio/Http2ALPNTest.java     | 207 +++++++++++++++++++
 3 files changed, 227 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/001c9012/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/Http2OnlyClientProtocolNegotiator.java
----------------------------------------------------------------------
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/Http2OnlyClientProtocolNegotiator.java
b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/Http2OnlyClientProtocolNegotiator.java
index 9a00e36..7bfaff1 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/Http2OnlyClientProtocolNegotiator.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/Http2OnlyClientProtocolNegotiator.java
@@ -39,12 +39,11 @@ import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
 import org.apache.hc.core5.http.ConnectionClosedException;
 import org.apache.hc.core5.http.EndpointDetails;
+import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.ProtocolVersion;
 import org.apache.hc.core5.http.impl.nio.HttpConnectionEventHandler;
 import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler;
 import org.apache.hc.core5.http.nio.command.ExecutionCommand;
-import org.apache.hc.core5.http2.H2ConnectionException;
-import org.apache.hc.core5.http2.H2Error;
 import org.apache.hc.core5.http2.ssl.ApplicationProtocols;
 import org.apache.hc.core5.io.ShutdownType;
 import org.apache.hc.core5.reactor.Command;
@@ -53,6 +52,7 @@ import org.apache.hc.core5.reactor.IOSession;
 import org.apache.hc.core5.reactor.TlsCapableIOSession;
 import org.apache.hc.core5.reactor.ssl.TlsDetails;
 import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.TextUtils;
 
 /**
  * @since 5.0
@@ -62,14 +62,17 @@ public class Http2OnlyClientProtocolNegotiator implements HttpConnectionEventHan
 
     private final TlsCapableIOSession ioSession;
     private final ClientHttp2StreamMultiplexerFactory http2StreamHandlerFactory;
+    private final boolean strictALPNHandshake;
 
     private final ByteBuffer preface;
 
     public Http2OnlyClientProtocolNegotiator(
             final TlsCapableIOSession ioSession,
-            final ClientHttp2StreamMultiplexerFactory http2StreamHandlerFactory) {
+            final ClientHttp2StreamMultiplexerFactory http2StreamHandlerFactory,
+            final boolean strictALPNHandshake) {
         this.ioSession = Args.notNull(ioSession, "I/O session");
         this.http2StreamHandlerFactory = Args.notNull(http2StreamHandlerFactory, "HTTP/2
stream handler factory");
+        this.strictALPNHandshake = strictALPNHandshake;
         this.preface = ByteBuffer.wrap(ClientHttpProtocolNegotiator.PREFACE);
     }
 
@@ -79,10 +82,13 @@ public class Http2OnlyClientProtocolNegotiator implements HttpConnectionEventHan
             final TlsDetails tlsDetails = ioSession.getTlsDetails();
             if (tlsDetails != null) {
                 final String applicationProtocol = tlsDetails.getApplicationProtocol();
-                if (applicationProtocol != null) {
+                if (TextUtils.isEmpty(applicationProtocol)) {
+                    if (strictALPNHandshake) {
+                        throw new HttpException("ALPN: missing application protocol");
+                    }
+                } else {
                     if (!ApplicationProtocols.HTTP_2.id.equals(applicationProtocol)) {
-                        throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Unexpected
application protocol: " +
-                                applicationProtocol);
+                        throw new HttpException("ALPN: unexpected application protocol '"
+ applicationProtocol + "'");
                     }
                 }
             }

http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/001c9012/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/Http2MultiplexingRequesterBootstrap.java
----------------------------------------------------------------------
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/Http2MultiplexingRequesterBootstrap.java
b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/Http2MultiplexingRequesterBootstrap.java
index e9b33e7..982120d 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/Http2MultiplexingRequesterBootstrap.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/Http2MultiplexingRequesterBootstrap.java
@@ -65,6 +65,7 @@ public class Http2MultiplexingRequesterBootstrap {
     private CharCodingConfig charCodingConfig;
     private H2Config h2Config;
     private TlsStrategy tlsStrategy;
+    private boolean strictALPNHandshake;
     private Decorator<IOSession> ioSessionDecorator;
     private IOSessionListener sessionListener;
     private Http2StreamListener streamListener;
@@ -117,6 +118,12 @@ public class Http2MultiplexingRequesterBootstrap {
         return this;
     }
 
+
+    public final Http2MultiplexingRequesterBootstrap setStrictALPNHandshake(final boolean
strictALPNHandshake) {
+        this.strictALPNHandshake = strictALPNHandshake;
+        return this;
+    }
+
     /**
      * Assigns {@link IOSession} {@link Decorator} instance.
      */
@@ -196,7 +203,7 @@ public class Http2MultiplexingRequesterBootstrap {
 
                     @Override
                     public IOEventHandler createHandler(final TlsCapableIOSession ioSession,
final Object attachment) {
-                        return new Http2OnlyClientProtocolNegotiator(ioSession, http2StreamHandlerFactory);
+                        return new Http2OnlyClientProtocolNegotiator(ioSession, http2StreamHandlerFactory,
strictALPNHandshake);
                     }
 
                 },

http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/001c9012/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2ALPNTest.java
----------------------------------------------------------------------
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2ALPNTest.java
b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2ALPNTest.java
new file mode 100644
index 0000000..bae22f4
--- /dev/null
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2ALPNTest.java
@@ -0,0 +1,207 @@
+/*
+ * ====================================================================
+ * 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 java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.List;
+import java.util.concurrent.Future;
+
+import org.apache.hc.core5.function.Supplier;
+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.URIScheme;
+import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
+import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
+import org.apache.hc.core5.http.nio.BasicRequestProducer;
+import org.apache.hc.core5.http.nio.BasicResponseConsumer;
+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.BasicServerTlsStrategy;
+import org.apache.hc.core5.http.nio.ssl.SecurePortStrategy;
+import org.apache.hc.core5.http2.impl.nio.bootstrap.H2ServerBootstrap;
+import org.apache.hc.core5.http2.impl.nio.bootstrap.Http2MultiplexingRequester;
+import org.apache.hc.core5.http2.impl.nio.bootstrap.Http2MultiplexingRequesterBootstrap;
+import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy;
+import org.apache.hc.core5.io.ShutdownType;
+import org.apache.hc.core5.reactor.ExceptionEvent;
+import org.apache.hc.core5.reactor.IOReactorConfig;
+import org.apache.hc.core5.reactor.ListenerEndpoint;
+import org.apache.hc.core5.testing.SSLTestContexts;
+import org.apache.hc.core5.testing.TestingSupport;
+import org.apache.hc.core5.util.Timeout;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.hamcrest.CoreMatchers;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExternalResource;
+
+public class Http2ALPNTest {
+
+    private final Logger log = LogManager.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 = H2ServerBootstrap.bootstrap()
+                    .setIOReactorConfig(
+                            IOReactorConfig.custom()
+                                    .setSoTimeout(TIMEOUT)
+                                    .build())
+                    .setTlsStrategy(new BasicServerTlsStrategy(
+                            SSLTestContexts.createServerSSLContext(),
+                            new SecurePortStrategy() {
+
+                                @Override
+                                public boolean isSecure(final SocketAddress localAddress)
{
+                                    return true;
+                                }
+
+                            }))
+                    .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
+                    .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
+                    .setStreamListener(LoggingHttp2StreamListener.INSTANCE)
+                    .register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+                        @Override
+                        public AsyncServerExchangeHandler get() {
+                            return new EchoHandler(2048);
+                        }
+
+                    })
+                    .create();
+        }
+
+        @Override
+        protected void after() {
+            log.debug("Shutting down test server");
+            if (server != null) {
+                try {
+                    server.shutdown(ShutdownType.GRACEFUL);
+                    final List<ExceptionEvent> exceptionLog = server.getExceptionLog();
+                    server = null;
+                    if (!exceptionLog.isEmpty()) {
+                        for (final ExceptionEvent event: exceptionLog) {
+                            final Throwable cause = event.getCause();
+                            log.error("Unexpected " + cause.getClass() + " at " + event.getTimestamp(),
cause);
+                        }
+                    }
+                } catch (final Exception ignore) {
+                }
+            }
+        }
+
+    };
+
+    private Http2MultiplexingRequester requester;
+
+    @Rule
+    public ExternalResource clientResource = new ExternalResource() {
+
+        @Override
+        protected void after() {
+            log.debug("Shutting down test client");
+            if (requester != null) {
+                try {
+                    requester.shutdown(ShutdownType.GRACEFUL);
+                    final List<ExceptionEvent> exceptionLog = requester.getExceptionLog();
+                    requester = null;
+                    if (!exceptionLog.isEmpty()) {
+                        for (final ExceptionEvent event: exceptionLog) {
+                            final Throwable cause = event.getCause();
+                            log.error("Unexpected " + cause.getClass() + " at " + event.getTimestamp(),
cause);
+                        }
+                    }
+                } catch (final Exception ignore) {
+                }
+            }
+        }
+
+    };
+
+    private static int javaVersion;
+
+    @BeforeClass
+    public static void determineJavaVersion() {
+        javaVersion = TestingSupport.determineJRELevel();
+    }
+
+    @Before
+    public void checkVersion() {
+        Assume.assumeTrue("Java version must be 9 or greater",  javaVersion >= 9);
+    }
+
+    @Test
+    public void testALPNLax() throws Exception {
+        log.debug("Starting up test client");
+        requester = Http2MultiplexingRequesterBootstrap.bootstrap()
+                .setIOReactorConfig(IOReactorConfig.custom()
+                        .setSoTimeout(TIMEOUT)
+                        .build())
+                .setTlsStrategy(new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext()))
+                .setStrictALPNHandshake(false)
+                .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
+                .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
+                .setStreamListener(LoggingHttp2StreamListener.INSTANCE)
+                .create();
+
+        server.start();
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final ListenerEndpoint listener = future.get();
+        final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
+        requester.start();
+
+        final HttpHost target = new HttpHost("localhost", address.getPort(), URIScheme.HTTPS.id);
+        final Future<Message<HttpResponse, String>> resultFuture1 = requester.execute(
+                new BasicRequestProducer("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());
+        Assert.assertThat(message1, CoreMatchers.notNullValue());
+        final HttpResponse response1 = message1.getHead();
+        Assert.assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
+        final String body1 = message1.getBody();
+        Assert.assertThat(body1, CoreMatchers.equalTo("some stuff"));
+    }
+
+}


Mime
View raw message