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

reta pushed a commit to branch 3.6.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit 40f09e6a0db63c8cddbd937bc4e26c3dd7ab7425
Author: Andriy Redko <[email protected]>
AuthorDate: Sat May 6 09:36:32 2023 -0400

    CXF-8606: Introduce HTTP/2 Transport: client-side support (#1017)
    
    (cherry picked from commit a744e7f595486ea34a0e0edc255d60a353942e81)
    
    # Conflicts:
    #       
systests/transport-netty/src/test/java/org/apache/cxf/systest/http2/netty/Http2TestClient.java
---
 .../http/asyncclient/hc5/AsyncHTTPConduit.java     |  16 +
 .../asyncclient/hc5/AsyncHTTPConduitFactory.java   |  10 +-
 rt/transports/http-netty/netty-client/pom.xml      |   6 +
 .../client/NettyHttpClientPipelineFactory.java     | 201 +++++++++-
 .../http/netty/client/NettyHttpClientRequest.java  |  15 +-
 .../http/netty/client/NettyHttpConduit.java        |  45 ++-
 .../org/apache/cxf/transport/http/HTTPConduit.java |   1 -
 .../src/main/resources/schemas/wsdl/http-conf.xsd  |   8 +
 systests/transport-hc5/pom.xml                     |  16 +
 .../AbstractApacheClientServerHttp2Test.java}      | 109 +++---
 .../systest/hc5/http2/AbstractBookServerHttp2.java |  64 ++++
 .../hc5/http2/ApacheClientServerHttp2Test.java     |  48 +++
 .../hc5/http2/ApacheClientServerHttp2cTest.java    |  47 +++
 .../org/apache/cxf/systest/hc5/http2/Book.java     |  69 ++++
 .../cxf/systest/hc5/http2/BookServerHttp2.java     |  47 +++
 .../cxf/systest/hc5/http2/BookServerHttp2c.java    |  45 +++
 .../apache/cxf/systest/hc5/http2/BookStore.java    |  67 ++++
 .../apache/cxf/systest/hc5/http2/server-tls.xml    |  41 +++
 .../org/apache/cxf/systest/hc5/http2/server.xml    |  32 ++
 systests/transport-netty/pom.xml                   |   5 +
 .../netty/AbstractNettyClientServerHttp2Test.java  |  87 ++---
 .../cxf/systest/http2/netty/Http2TestClient.java   | 405 ---------------------
 22 files changed, 862 insertions(+), 522 deletions(-)

diff --git 
a/rt/transports/http-hc5/src/main/java/org/apache/cxf/transport/http/asyncclient/hc5/AsyncHTTPConduit.java
 
b/rt/transports/http-hc5/src/main/java/org/apache/cxf/transport/http/asyncclient/hc5/AsyncHTTPConduit.java
index 1e05f6fa8c..3e62e3ff37 100644
--- 
a/rt/transports/http-hc5/src/main/java/org/apache/cxf/transport/http/asyncclient/hc5/AsyncHTTPConduit.java
+++ 
b/rt/transports/http-hc5/src/main/java/org/apache/cxf/transport/http/asyncclient/hc5/AsyncHTTPConduit.java
@@ -98,6 +98,10 @@ import org.apache.hc.core5.util.Timeout;
  * Async HTTP Conduit using Apache HttpClient 5
  */
 public class AsyncHTTPConduit extends HttpClientHTTPConduit {
+    /**
+     * Enable HTTP/2 support
+     */
+    public static final String ENABLE_HTTP2 = 
"org.apache.cxf.transports.http2.enabled";
     public static final String USE_ASYNC = "use.async.http.conduit";
 
     private final AsyncHTTPConduitFactory factory;
@@ -137,7 +141,10 @@ public class AsyncHTTPConduit extends 
HttpClientHTTPConduit {
             super.setupConnection(message, address, csPolicy);
             return;
         }
+
         propagateJaxwsSpecTimeoutSettings(message, csPolicy);
+        propagateProtocolSettings(message, csPolicy);
+
         boolean addressChanged = false;
         // need to do some clean up work on the URI address
         URI uri = address.getURI();
@@ -252,6 +259,15 @@ public class AsyncHTTPConduit extends 
HttpClientHTTPConduit {
         message.put(CXFHttpRequest.class, e);
     }
 
+    private void propagateProtocolSettings(Message message, HTTPClientPolicy 
csPolicy) {
+        if (message != null) {
+            final Object o = message.getContextualProperty(ENABLE_HTTP2);
+            if (o != null) {
+                csPolicy.setEnableHttp2(PropertyUtils.isTrue(o));
+            }
+        }
+    }
+
     private void propagateJaxwsSpecTimeoutSettings(Message message, 
HTTPClientPolicy csPolicy) {
         int receiveTimeout = determineReceiveTimeout(message, csPolicy);
         if (csPolicy.getReceiveTimeout() == 60000) {
diff --git 
a/rt/transports/http-hc5/src/main/java/org/apache/cxf/transport/http/asyncclient/hc5/AsyncHTTPConduitFactory.java
 
b/rt/transports/http-hc5/src/main/java/org/apache/cxf/transport/http/asyncclient/hc5/AsyncHTTPConduitFactory.java
index f660288056..3b5f1ab6a1 100644
--- 
a/rt/transports/http-hc5/src/main/java/org/apache/cxf/transport/http/asyncclient/hc5/AsyncHTTPConduitFactory.java
+++ 
b/rt/transports/http-hc5/src/main/java/org/apache/cxf/transport/http/asyncclient/hc5/AsyncHTTPConduitFactory.java
@@ -38,6 +38,7 @@ import org.apache.cxf.transport.http.HTTPTransportFactory;
 import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
 import org.apache.cxf.ws.addressing.EndpointReferenceType;
 import org.apache.hc.client5.http.SystemDefaultDnsResolver;
+import org.apache.hc.client5.http.config.TlsConfig;
 import org.apache.hc.client5.http.cookie.BasicCookieStore;
 import org.apache.hc.client5.http.cookie.Cookie;
 import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
@@ -54,6 +55,7 @@ import org.apache.hc.core5.http.config.Lookup;
 import org.apache.hc.core5.http.config.RegistryBuilder;
 import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
 import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.http2.HttpVersionPolicy;
 import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
 import org.apache.hc.core5.pool.PoolReusePolicy;
 import org.apache.hc.core5.reactor.IOReactorConfig;
@@ -351,6 +353,13 @@ public class AsyncHTTPConduitFactory implements 
HTTPConduitFactory {
         connectionManager.setDefaultMaxPerRoute(maxPerRoute);
         connectionManager.setMaxTotal(maxConnections);
 
+        if (Boolean.FALSE.equals(clientPolicy.isEnableHttp2())) {
+            connectionManager.setDefaultTlsConfig(TlsConfig
+                .custom()
+                .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1)
+                .build());
+        }
+
         final RedirectStrategy redirectStrategy = new RedirectStrategy() {
             public boolean isRedirected(HttpRequest request, HttpResponse 
response, HttpContext context)
                     throws ProtocolException {
@@ -371,7 +380,6 @@ public class AsyncHTTPConduitFactory implements 
HTTPConduitFactory {
                 public void addCookie(Cookie cookie) {
                 }
             });
-
         adaptClientBuilder(httpAsyncClientBuilder);
 
         final CloseableHttpAsyncClient client = httpAsyncClientBuilder
diff --git a/rt/transports/http-netty/netty-client/pom.xml 
b/rt/transports/http-netty/netty-client/pom.xml
index 0ff7193de3..fad0b863a8 100644
--- a/rt/transports/http-netty/netty-client/pom.xml
+++ b/rt/transports/http-netty/netty-client/pom.xml
@@ -49,6 +49,12 @@
             <artifactId>netty-codec-http</artifactId>
             <version>${cxf.netty.version}</version>
         </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-codec-http2</artifactId>
+            <version>${cxf.netty.version}</version>
+            <optional>true</optional>
+        </dependency>
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-core</artifactId>
diff --git 
a/rt/transports/http-netty/netty-client/src/main/java/org/apache/cxf/transport/http/netty/client/NettyHttpClientPipelineFactory.java
 
b/rt/transports/http-netty/netty-client/src/main/java/org/apache/cxf/transport/http/netty/client/NettyHttpClientPipelineFactory.java
index 658c5660f4..be44b9c4ec 100644
--- 
a/rt/transports/http-netty/netty-client/src/main/java/org/apache/cxf/transport/http/netty/client/NettyHttpClientPipelineFactory.java
+++ 
b/rt/transports/http-netty/netty-client/src/main/java/org/apache/cxf/transport/http/netty/client/NettyHttpClientPipelineFactory.java
@@ -19,27 +19,57 @@
 
 package org.apache.cxf.transport.http.netty.client;
 
+import java.security.KeyStore;
 import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.ManagerFactoryParameters;
 import javax.net.ssl.SSLEngine;
+import javax.net.ssl.TrustManager;
 
 import org.apache.cxf.common.logging.LogUtils;
 import org.apache.cxf.configuration.jsse.TLSClientParameters;
+import org.apache.cxf.transport.https.SSLContextInitParameters;
 import org.apache.cxf.transport.https.SSLUtils;
 
 import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelInitializer;
 import io.netty.channel.ChannelPipeline;
+import io.netty.channel.ChannelPromise;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.handler.codec.http.HttpClientCodec;
+import io.netty.handler.codec.http.HttpClientUpgradeHandler;
 import io.netty.handler.codec.http.HttpObjectAggregator;
 import io.netty.handler.codec.http.HttpRequestEncoder;
 import io.netty.handler.codec.http.HttpResponseDecoder;
+import io.netty.handler.codec.http2.DefaultHttp2Connection;
+import io.netty.handler.codec.http2.DelegatingDecompressorFrameListener;
+import io.netty.handler.codec.http2.Http2ClientUpgradeCodec;
+import io.netty.handler.codec.http2.Http2Connection;
+import io.netty.handler.codec.http2.Http2ConnectionHandler;
+import io.netty.handler.codec.http2.Http2SecurityUtil;
+import io.netty.handler.codec.http2.Http2Settings;
+import io.netty.handler.codec.http2.HttpToHttp2ConnectionHandlerBuilder;
+import io.netty.handler.codec.http2.InboundHttp2ToHttpAdapterBuilder;
+import io.netty.handler.ssl.ApplicationProtocolConfig;
+import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
+import 
io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
+import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
+import io.netty.handler.ssl.ApplicationProtocolNames;
+import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
 import io.netty.handler.ssl.SslHandler;
+import io.netty.handler.ssl.SupportedCipherSuiteFilter;
+import io.netty.handler.ssl.util.SimpleKeyManagerFactory;
+import io.netty.handler.ssl.util.SimpleTrustManagerFactory;
 import io.netty.handler.stream.ChunkedWriteHandler;
 import io.netty.handler.timeout.ReadTimeoutHandler;
 
-
 public class NettyHttpClientPipelineFactory extends 
ChannelInitializer<Channel> {
 
     private static final Logger LOG =
@@ -48,6 +78,8 @@ public class NettyHttpClientPipelineFactory extends 
ChannelInitializer<Channel>
     private final TLSClientParameters tlsClientParameters;
     private final int readTimeout;
     private final int maxContentLength;
+    private final boolean enableHttp2;
+    private ChannelPromise readyFuture;
 
     public NettyHttpClientPipelineFactory(TLSClientParameters 
clientParameters) {
         this(clientParameters, 0);
@@ -59,16 +91,22 @@ public class NettyHttpClientPipelineFactory extends 
ChannelInitializer<Channel>
 
     public NettyHttpClientPipelineFactory(TLSClientParameters 
clientParameters, int readTimeout,
                                           int maxResponseContentLength) {
+        this(clientParameters, readTimeout, maxResponseContentLength, false);
+    }
+    
+    public NettyHttpClientPipelineFactory(TLSClientParameters 
clientParameters, int readTimeout,
+            int maxResponseContentLength, boolean enableHttp2) {
         this.tlsClientParameters = clientParameters;
         this.readTimeout = readTimeout;
         this.maxContentLength = maxResponseContentLength;
+        this.enableHttp2 = enableHttp2;
     }
 
     @Override
     protected void initChannel(Channel ch) throws Exception {
         ChannelPipeline pipeline = ch.pipeline();
 
-        SslHandler sslHandler = configureClientSSLOnDemand();
+        SslHandler sslHandler = configureClientSSLOnDemand(ch);
         if (sslHandler != null) {
             LOG.log(Level.FINE,
                     "Server SSL handler configured and added as an interceptor 
against the ChannelPipeline: {}",
@@ -76,25 +114,166 @@ public class NettyHttpClientPipelineFactory extends 
ChannelInitializer<Channel>
             pipeline.addLast("ssl", sslHandler);
         }
 
+        final NettyHttpClientHandler responseHandler = new 
NettyHttpClientHandler();
+        readyFuture = ch.newPromise();
+        
+        if (enableHttp2) {
+            final Http2Connection connection = new 
DefaultHttp2Connection(false);
+            final Http2SettingsHandler settingsHandler = new 
Http2SettingsHandler(readyFuture);
+            
+            Http2ConnectionHandler connectionHandler = new 
HttpToHttp2ConnectionHandlerBuilder()
+                .connection(connection)
+                .frameListener(new 
DelegatingDecompressorFrameListener(connection,
+                    new InboundHttp2ToHttpAdapterBuilder(connection)
+                        .maxContentLength(maxContentLength)
+                        .propagateSettings(true)
+                        .build()))
+                .build();
+
+            if (sslHandler != null) {
+                // Wait for the handshake to finish and the protocol to be 
negotiated before 
+                // configuring the HTTP/2 components of the pipeline.
+                pipeline.addLast(new 
ApplicationProtocolNegotiationHandler(ApplicationProtocolNames.HTTP_1_1) {
+                    @Override
+                    protected void configurePipeline(ChannelHandlerContext 
ctx, String protocol) {
+                        final ChannelPipeline p = ctx.pipeline();
+                        if (ApplicationProtocolNames.HTTP_2.equals(protocol)) {
+                            p.addLast(connectionHandler);
+                            p.addLast(settingsHandler);
+                            p.addLast("client", responseHandler);
+                        } else if 
(ApplicationProtocolNames.HTTP_1_1.equals(protocol)) {
+                            p.addLast("decoder", new HttpResponseDecoder());
+                            p.addLast("aggregator", new 
HttpObjectAggregator(maxContentLength));
+                            p.addLast("encoder", new HttpRequestEncoder());
+                            p.addLast("chunkedWriter", new 
ChunkedWriteHandler());
+                            readyFuture.setSuccess(null);
+                        } else {
+                            ctx.close();
+                            final IllegalStateException ex = new 
IllegalStateException("Unknown protocol: " + protocol);
+                            readyFuture.setFailure(ex);
+                            throw ex;
+                        }
+                    }
+                });
+                
+                if (readTimeout > 0) {
+                    pipeline.addLast("readTimeoutHandler", new 
ReadTimeoutHandler(readTimeout, TimeUnit.MILLISECONDS));
+                }
+            } else {
+                final HttpClientCodec sourceCodec = new HttpClientCodec();
+                final Http2ClientUpgradeCodec upgradeCodec = new 
Http2ClientUpgradeCodec(connectionHandler);
+                final HttpClientUpgradeHandler upgradeHandler = new 
HttpClientUpgradeHandler(sourceCodec, 
+                    upgradeCodec, maxContentLength);
 
-        pipeline.addLast("decoder", new HttpResponseDecoder());
-        pipeline.addLast("aggregator", new 
HttpObjectAggregator(maxContentLength));
-        pipeline.addLast("encoder", new HttpRequestEncoder());
-        pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
-        if (readTimeout > 0) {
-            pipeline.addLast("readTimeoutHandler", new 
ReadTimeoutHandler(readTimeout, TimeUnit.MILLISECONDS));
+                pipeline.addLast(sourceCodec);
+                pipeline.addLast(upgradeHandler);
+
+                if (readTimeout > 0) {
+                    pipeline.addLast("readTimeoutHandler", new 
ReadTimeoutHandler(readTimeout, TimeUnit.MILLISECONDS));
+                }
+                
+                pipeline.addLast("client", responseHandler);
+                readyFuture.setSuccess(null);
+            }
+        } else {
+            pipeline.addLast("decoder", new HttpResponseDecoder());
+            pipeline.addLast("aggregator", new 
HttpObjectAggregator(maxContentLength));
+            pipeline.addLast("encoder", new HttpRequestEncoder());
+            pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
+            if (readTimeout > 0) {
+                pipeline.addLast("readTimeoutHandler", new 
ReadTimeoutHandler(readTimeout, TimeUnit.MILLISECONDS));
+            }
+            pipeline.addLast("client", responseHandler);
+            readyFuture.setSuccess(null);
         }
-        pipeline.addLast("client", new NettyHttpClientHandler());
     }
 
+    public ChannelFuture whenReady() {
+        return readyFuture; 
+    }
 
-    private SslHandler configureClientSSLOnDemand() throws Exception {
+    private SslHandler configureClientSSLOnDemand(Channel channel) throws 
Exception {
         if (tlsClientParameters != null) {
-            SSLEngine sslEngine = 
SSLUtils.createClientSSLEngine(tlsClientParameters);
+            final SSLEngine sslEngine;
+
+            if (enableHttp2) {
+                final SSLContextInitParameters initParams = 
SSLUtils.getSSLContextInitParameters(tlsClientParameters);
+
+                sslEngine = SslContextBuilder
+                    .forClient()
+                    .sslProvider(SslContext.defaultClientProvider())
+                    .keyManager(new SimpleKeyManagerFactory() {
+                        
+                        @Override
+                        protected void engineInit(KeyStore keyStore, char[] 
var2) throws Exception {
+                        }
+                        
+                        @Override
+                        protected void engineInit(ManagerFactoryParameters 
params) throws Exception {
+                        }
+                        
+                        @Override
+                        protected KeyManager[] engineGetKeyManagers() {
+                            final KeyManager[] keyManagers = 
initParams.getKeyManagers();
+                            if (keyManagers == null) {
+                                return new KeyManager[0];
+                            }
+                            return keyManagers;
+                        }
+                    })
+                    .trustManager(new SimpleTrustManagerFactory() {
+                        @Override
+                        protected void engineInit(KeyStore keyStore) throws 
Exception {
+                        }
+
+                        @Override
+                        protected void engineInit(ManagerFactoryParameters 
params) throws Exception {
+                        }
+
+                        @Override
+                        protected TrustManager[] engineGetTrustManagers() {
+                            final TrustManager[] trustManagers = 
initParams.getTrustManagers();
+                            if (trustManagers == null) {
+                                return new TrustManager[0];
+                            }
+                            return trustManagers;
+                        }
+                        
+                    })
+                    .ciphers(Http2SecurityUtil.CIPHERS, 
SupportedCipherSuiteFilter.INSTANCE)
+                    .applicationProtocolConfig(
+                        new ApplicationProtocolConfig(
+                            Protocol.ALPN,
+                            // NO_ADVERTISE is currently the only mode 
supported by both OpenSsl and JDK providers.
+                            SelectorFailureBehavior.NO_ADVERTISE,
+                            // ACCEPT is currently the only mode supported by 
both OpenSsl and JDK providers.
+                            SelectedListenerFailureBehavior.ACCEPT,
+                            ApplicationProtocolNames.HTTP_2,
+                            ApplicationProtocolNames.HTTP_1_1
+                        )
+                    )
+                    .build()
+                    .newEngine(channel.alloc());
+            } else {
+                sslEngine = 
SSLUtils.createClientSSLEngine(tlsClientParameters);
+            }
+
             return new SslHandler(sslEngine);
         }
         return null;
     }
 
+    private static class Http2SettingsHandler extends 
SimpleChannelInboundHandler<Http2Settings> {
+        private ChannelPromise promise;
 
+        Http2SettingsHandler(ChannelPromise promise) {
+            this.promise = promise;
+        }
+
+        @Override
+        protected void channelRead0(ChannelHandlerContext ctx, Http2Settings 
msg) throws Exception {
+            promise.setSuccess();
+            ctx.pipeline().remove(this);
+        }
+    }
 }
diff --git 
a/rt/transports/http-netty/netty-client/src/main/java/org/apache/cxf/transport/http/netty/client/NettyHttpClientRequest.java
 
b/rt/transports/http-netty/netty-client/src/main/java/org/apache/cxf/transport/http/netty/client/NettyHttpClientRequest.java
index d42d214e3b..789c293954 100644
--- 
a/rt/transports/http-netty/netty-client/src/main/java/org/apache/cxf/transport/http/netty/client/NettyHttpClientRequest.java
+++ 
b/rt/transports/http-netty/netty-client/src/main/java/org/apache/cxf/transport/http/netty/client/NettyHttpClientRequest.java
@@ -27,21 +27,28 @@ import io.netty.handler.codec.http.HttpMethod;
 import io.netty.handler.codec.http.HttpRequest;
 import io.netty.handler.codec.http.HttpResponse;
 import io.netty.handler.codec.http.HttpVersion;
+import io.netty.handler.codec.http2.HttpConversionUtil.ExtensionHeaderNames;
 
 public class NettyHttpClientRequest {
 
     private HttpRequest request;
     private HttpResponse response;
-    private URI uri;
-    private String method;
+    private final URI uri;
+    private final String method;
     private CxfResponseCallBack cxfResponseCallback;
     private int connectionTimeout;
     private int receiveTimeout;
     private int maxResponseContentLength;
+    private final boolean enableHttp2;
 
     public NettyHttpClientRequest(URI requestUri, String method) {
+        this(requestUri, method, false);
+    }
+
+    public NettyHttpClientRequest(URI requestUri, String method, boolean 
enableHttp2) {
         this.uri = requestUri;
         this.method = method;
+        this.enableHttp2 = enableHttp2;
     }
 
     public void createRequest(ByteBuf content) {
@@ -53,6 +60,10 @@ public class NettyHttpClientRequest {
         request.headers().set("Connection", "keep-alive");
         request.headers().set("Host", uri.getHost() + ":"
             + (uri.getPort() != -1 ? uri.getPort() : 
"http".equals(uri.getScheme()) ? 80 : 443));
+
+        if (enableHttp2) {
+            request.headers().set(ExtensionHeaderNames.SCHEME.text(), 
uri.getScheme());
+        }
     }
 
     public HttpRequest getRequest() {
diff --git 
a/rt/transports/http-netty/netty-client/src/main/java/org/apache/cxf/transport/http/netty/client/NettyHttpConduit.java
 
b/rt/transports/http-netty/netty-client/src/main/java/org/apache/cxf/transport/http/netty/client/NettyHttpConduit.java
index 2828890b24..b624e0c423 100644
--- 
a/rt/transports/http-netty/netty-client/src/main/java/org/apache/cxf/transport/http/netty/client/NettyHttpConduit.java
+++ 
b/rt/transports/http-netty/netty-client/src/main/java/org/apache/cxf/transport/http/netty/client/NettyHttpConduit.java
@@ -80,6 +80,10 @@ import io.netty.handler.ssl.SslHandler;
 
 
 public class NettyHttpConduit extends HttpClientHTTPConduit implements 
BusLifeCycleListener {
+    /**
+     * Enable HTTP/2 support
+     */
+    public static final String ENABLE_HTTP2 = 
"org.apache.cxf.transports.http2.enabled";
     public static final String USE_ASYNC = "use.async.http.conduit";
     public static final String MAX_RESPONSE_CONTENT_LENGTH =
         "org.apache.cxf.transport.http.netty.maxResponseContentLength";
@@ -107,6 +111,7 @@ public class NettyHttpConduit extends HttpClientHTTPConduit 
implements BusLifeCy
 
     // Using Netty API directly
     protected void setupConnection(Message message, Address address, 
HTTPClientPolicy csPolicy) throws IOException {
+        propagateProtocolSettings(message, csPolicy);
 
         URI uri = address.getURI();
         boolean addressChanged = false;
@@ -177,7 +182,8 @@ public class NettyHttpConduit extends HttpClientHTTPConduit 
implements BusLifeCy
             message.put(Message.HTTP_REQUEST_METHOD, httpRequestMethod);
         }
         // setup a new NettyHttpClientRequest
-        final NettyHttpClientRequest request = new NettyHttpClientRequest(uri, 
httpRequestMethod);
+        final boolean enableHttp2 = csPolicy.isSetEnableHttp2() && 
csPolicy.isEnableHttp2();
+        final NettyHttpClientRequest request = new NettyHttpClientRequest(uri, 
httpRequestMethod, enableHttp2);
         final int ctimeout = determineConnectionTimeout(message, csPolicy);
         final int rtimeout = determineReceiveTimeout(message, csPolicy);
         final int maxResponseContentLength = 
determineMaxResponseContentLength(message);
@@ -189,6 +195,15 @@ public class NettyHttpConduit extends 
HttpClientHTTPConduit implements BusLifeCy
 
     }
 
+    private void propagateProtocolSettings(Message message, HTTPClientPolicy 
csPolicy) {
+        if (message != null) {
+            final Object o = message.getContextualProperty(ENABLE_HTTP2);
+            if (o != null) {
+                csPolicy.setEnableHttp2(PropertyUtils.isTrue(o));
+            }
+        }
+    }
+
     protected OutputStream createOutputStream(Message message,
                                               boolean needToCacheRequest,
                                               boolean isChunking,
@@ -217,6 +232,7 @@ public class NettyHttpConduit extends HttpClientHTTPConduit 
implements BusLifeCy
 
     public class NettyWrappedOutputStream extends WrappedOutputStream {
         final HTTPClientPolicy csPolicy;
+        final boolean enableHttp2;
         NettyHttpClientRequest entity;
         volatile HttpResponse httpResponse;
         volatile Throwable exception;
@@ -238,6 +254,7 @@ public class NettyHttpConduit extends HttpClientHTTPConduit 
implements BusLifeCy
             int bufSize = csPolicy.getChunkLength() > 0 ? 
csPolicy.getChunkLength() : 16320;
             outBuffer = Unpooled.buffer(bufSize);
             outputStream = new ByteBufOutputStream(outBuffer);
+            enableHttp2 = csPolicy.isSetEnableHttp2() && 
csPolicy.isEnableHttp2();
         }
 
         protected ByteBuf getOutBuffer() {
@@ -281,7 +298,6 @@ public class NettyHttpConduit extends HttpClientHTTPConduit 
implements BusLifeCy
             return (HttpContent) getHttpResponse();
         }
 
-
         protected Channel getChannel() throws IOException {
             syncLock.lock();
             try {
@@ -371,22 +387,25 @@ public class NettyHttpConduit extends 
HttpClientHTTPConduit implements BusLifeCy
         }
 
         protected void connect(boolean output) {
+            final NettyHttpClientPipelineFactory handler; 
             if ("https".equals(url.getScheme())) {
                 TLSClientParameters clientParameters = 
findTLSClientParameters();
-                bootstrap.handler(new 
NettyHttpClientPipelineFactory(clientParameters, entity.getReceiveTimeout(),
-                    entity.getMaxResponseContentLength()));
+                handler = new NettyHttpClientPipelineFactory(clientParameters, 
entity.getReceiveTimeout(),
+                    entity.getMaxResponseContentLength(), enableHttp2);
             } else {
-                bootstrap.handler(new NettyHttpClientPipelineFactory(null, 
entity.getReceiveTimeout(),
-                    entity.getMaxResponseContentLength()));
+                handler = new NettyHttpClientPipelineFactory(null, 
entity.getReceiveTimeout(),
+                    entity.getMaxResponseContentLength(), enableHttp2);
             }
 
+            // Set handler
+            bootstrap.handler(handler);
+
             ChannelFuture connFuture =
                 bootstrap.connect(new InetSocketAddress(url.getHost(), 
url.getPort() != -1 ? url.getPort()
                                                             : 
"http".equals(url.getScheme()) ? 80 : 443));
 
             // Setup the call back on the NettyHttpClientRequest
             ChannelFutureListener listener = new ChannelFutureListener() {
-
                 @Override
                 public void operationComplete(ChannelFuture future) throws 
Exception {
                     if (future.isSuccess()) {
@@ -408,7 +427,6 @@ public class NettyHttpConduit extends HttpClientHTTPConduit 
implements BusLifeCy
 
             connFuture.addListener(listener);
 
-
             // setup the CxfResponseCallBack
             CxfResponseCallBack callBack = new CxfResponseCallBack() {
                 @Override
@@ -440,8 +458,15 @@ public class NettyHttpConduit extends 
HttpClientHTTPConduit implements BusLifeCy
                         @Override
                         public void operationComplete(ChannelFuture future) 
throws Exception {
                             if (future.isSuccess()) {
-                                ChannelFuture channelFuture = 
future.channel().writeAndFlush(entity);
-                                
channelFuture.addListener(writeFailureListener);
+                                handler.whenReady().addListener(new 
ChannelFutureListener() {
+                                    @Override
+                                    public void 
operationComplete(ChannelFuture future) throws Exception {
+                                        if (future.isSuccess()) {
+                                            ChannelFuture channelFuture = 
future.channel().writeAndFlush(entity);
+                                            
channelFuture.addListener(writeFailureListener);
+                                        }
+                                    }
+                                });
                             }
                         }
                     });
diff --git 
a/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java
 
b/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java
index 48a1232c07..83fc672244 100644
--- 
a/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java
+++ 
b/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java
@@ -153,7 +153,6 @@ public abstract class HTTPConduit
     extends AbstractConduit
     implements Configurable, Assertor, PropertyChangeListener {
 
-
     /**
      *  This constant is the Message(Map) key for the HttpURLConnection that
      *  is used to get the response.
diff --git a/rt/transports/http/src/main/resources/schemas/wsdl/http-conf.xsd 
b/rt/transports/http/src/main/resources/schemas/wsdl/http-conf.xsd
index ca3ac7b116..d34f53bd9e 100644
--- a/rt/transports/http/src/main/resources/schemas/wsdl/http-conf.xsd
+++ b/rt/transports/http/src/main/resources/schemas/wsdl/http-conf.xsd
@@ -400,6 +400,14 @@
                 </xs:documentation>
             </xs:annotation>      
         </xs:attribute>
+        <xs:attribute name="EnableHttp2" type="ptp:ParameterizedBoolean" 
use="optional" default="true">
+            <xs:annotation>
+                <xs:documentation>
+                If true, the client is free to use HTTP/2 if it supports it, 
but it is not 
+                required to do so. If false, the client must use HTTP/1.1 in 
all cases.
+                </xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
         <xs:anyAttribute namespace="http://schemas.xmlsoap.org/wsdl/"/>
     </xs:complexType>
     
diff --git a/systests/transport-hc5/pom.xml b/systests/transport-hc5/pom.xml
index a73cf81a1a..bd7c140ab6 100644
--- a/systests/transport-hc5/pom.xml
+++ b/systests/transport-hc5/pom.xml
@@ -97,6 +97,22 @@
             <groupId>org.apache.cxf</groupId>
             <artifactId>cxf-rt-transports-http-jetty</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.http2</groupId>
+            <artifactId>http2-server</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-alpn-server</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-alpn-java-server</artifactId>
+            <version>${cxf.jetty10.version}</version>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.apache.cxf</groupId>
             <artifactId>cxf-rt-frontend-jaxws</artifactId>
diff --git 
a/systests/transport-netty/src/test/java/org/apache/cxf/systest/http2/netty/AbstractNettyClientServerHttp2Test.java
 
b/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/AbstractApacheClientServerHttp2Test.java
similarity index 57%
copy from 
systests/transport-netty/src/test/java/org/apache/cxf/systest/http2/netty/AbstractNettyClientServerHttp2Test.java
copy to 
systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/AbstractApacheClientServerHttp2Test.java
index b1c2b5c436..9da63c85e2 100644
--- 
a/systests/transport-netty/src/test/java/org/apache/cxf/systest/http2/netty/AbstractNettyClientServerHttp2Test.java
+++ 
b/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/AbstractApacheClientServerHttp2Test.java
@@ -17,109 +17,123 @@
  * under the License.
  */
 
-package org.apache.cxf.systest.http2.netty;
+package org.apache.cxf.systest.hc5.http2;
 
 import javax.ws.rs.core.Response;
 
 import org.apache.cxf.configuration.jsse.TLSClientParameters;
 import org.apache.cxf.jaxrs.client.WebClient;
-import org.apache.cxf.systest.http2.netty.Http2TestClient.ClientResponse;
 import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
 import org.apache.cxf.transport.http.HTTPConduit;
+import org.apache.cxf.transport.http.asyncclient.hc5.AsyncHTTPConduit;
 import org.apache.cxf.transport.https.InsecureTrustManager;
 
 import org.junit.Test;
 
 import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
-abstract class AbstractNettyClientServerHttp2Test extends 
AbstractBusClientServerTestBase {
+abstract class AbstractApacheClientServerHttp2Test extends 
AbstractBusClientServerTestBase {
     @Test
     public void testBookNotFoundWithHttp2() throws Exception {
-        final Http2TestClient client = new Http2TestClient(isSecure());
+        final WebClient client = createWebClient("/web/bookstore/notFound", 
true);
+        assertThat(WebClient.getConfig(client).getHttpConduit(), 
instanceOf(AsyncHTTPConduit.class));
         
-        final ClientResponse response = client
-            .request(getAddress())
+        final Response response = client
             .accept("text/plain")
             .path(getContext() + "/web/bookstore/notFound")
-            .http2()
             .get();
         
-        assertThat(response.getResponseCode(), equalTo(404));
-        assertThat(response.getProtocol(), equalTo("HTTP/2.0"));
+        assertThat(response.getStatus(), equalTo(404));
     }
-    
+
     @Test
     public void testBookTraceWithHttp2() throws Exception {
-        final Http2TestClient client = new Http2TestClient(isSecure());
-        
-        final ClientResponse response = client
-            .request(getAddress())
+        final WebClient client = createWebClient("/web/bookstore/trace", true);
+        assertThat(WebClient.getConfig(client).getHttpConduit(), 
instanceOf(AsyncHTTPConduit.class));
+
+        final Response response = client
             .accept("text/plain")
-            .path(getContext() + "/web/bookstore/trace")
-            .http2()
-            .trace();
+            .invoke("TRACE", null);
         
-        assertThat(response.getResponseCode(), equalTo(406));
-        assertThat(response.getProtocol(), equalTo("HTTP/2.0"));
+        // Apache CXF Jetty transport does not allow TRACE HTTP verb
+        assertThat(response.getStatus(), equalTo(405));
+
+        client.close();
     }
     
     @Test
     public void testBookWithHttp2() throws Exception {
-        final Http2TestClient client = new Http2TestClient(isSecure());
+        final WebClient client = createWebClient("/web/bookstore/booknames", 
true);
+        assertThat(WebClient.getConfig(client).getHttpConduit(), 
instanceOf(AsyncHTTPConduit.class));
         
-        final ClientResponse response = client
-            .request(getAddress())
+        final Response response = client
             .accept("text/plain")
-            .path(getContext() + "/web/bookstore/booknames")
-            .http2()
             .get();
         
-        assertThat(response.getResponseCode(), equalTo(200));
-        assertThat(response.getProtocol(), equalTo("HTTP/2.0"));
-        assertEquals("CXF in Action", response.getBody());
+        assertThat(response.getStatus(), equalTo(200));
+        assertEquals("CXF in Action", response.readEntity(String.class));
+
+        client.close();
     }
 
     @Test
     public void testGetBookStreamHttp2() throws Exception {
-        final Http2TestClient client = new Http2TestClient(isSecure());
+        final WebClient client = createWebClient("/web/bookstore/bookstream", 
true);
+        assertThat(WebClient.getConfig(client).getHttpConduit(), 
instanceOf(AsyncHTTPConduit.class));
         
-        final ClientResponse response = client
-            .request(getAddress())
+        final Response response = client
             .accept("application/xml")
-            .path(getContext() + "/web/bookstore/bookstream")
-            .http2()
             .get();
 
-        assertThat(response.getResponseCode(), equalTo(200));
-        assertThat(response.getProtocol(), equalTo("HTTP/2.0"));
+        assertThat(response.getStatus(), equalTo(200));
         assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\" 
standalone=\"yes\"?>"
-            + "<Book><id>1</id><name>Book1</name></Book>", response.getBody());
+            + "<Book><id>1</id><name>Book1</name></Book>", 
response.readEntity(String.class));
+
+        client.close();
     }
 
     @Test
     public void testBookWithHttp() throws Exception {
-        final WebClient wc = createWebClient("/web/bookstore/booknames");
-        try (Response resp = wc.get()) {
+        final WebClient client = createWebClient("/web/bookstore/booknames", 
false);
+        
+        try (Response resp = client.get()) {
             assertThat(resp.getStatus(), equalTo(200));
             assertEquals("CXF in Action", resp.readEntity(String.class));
         }
+        
+        client.close();
     }
 
     @Test
     public void testBookTraceWithHttp() throws Exception {
-        final WebClient wc = createWebClient("/web/bookstore/trace");
-        try (Response response = wc.invoke("TRACE", null)) {
-            assertThat(response.getStatus(), equalTo(406));
-        }        
+        final WebClient client = createWebClient("/web/bookstore/trace", 
false);
+
+        // Apache CXF Jetty transport does not allow TRACE HTTP verb
+        try (Response response = client.invoke("TRACE", null)) {
+            assertThat(response.getStatus(), equalTo(405));
+        }
+
+        client.close();
     }
 
-    private WebClient createWebClient(final String path) {
+    protected abstract String getAddress();
+    protected abstract String getContext();
+
+    protected boolean isSecure() {
+        return getAddress().startsWith("https");
+    }
+    
+    private WebClient createWebClient(final String path, final boolean 
enableHttp2) {
         final WebClient wc = WebClient
             .create(getAddress() + getContext() + path)
             .accept("text/plain");
-        
+
+        
WebClient.getConfig(wc).getRequestContext().put(AsyncHTTPConduit.ENABLE_HTTP2, 
enableHttp2);
+        
WebClient.getConfig(wc).getRequestContext().put(AsyncHTTPConduit.USE_ASYNC, 
"ALWAYS");
+
         if (isSecure()) {
             final HTTPConduit conduit = 
WebClient.getConfig(wc).getHttpConduit();
             TLSClientParameters params = conduit.getTlsClientParameters();
@@ -136,11 +150,4 @@ abstract class AbstractNettyClientServerHttp2Test extends 
AbstractBusClientServe
         
         return wc;
     }
-
-    protected abstract String getAddress();
-    protected abstract String getContext();
-
-    protected boolean isSecure() {
-        return getAddress().startsWith("https");
-    }
-}
\ No newline at end of file
+}
diff --git 
a/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/AbstractBookServerHttp2.java
 
b/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/AbstractBookServerHttp2.java
new file mode 100644
index 0000000000..920e70512e
--- /dev/null
+++ 
b/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/AbstractBookServerHttp2.java
@@ -0,0 +1,64 @@
+/**
+ * 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.
+ */
+
+package org.apache.cxf.systest.hc5.http2;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.bus.spring.SpringBusFactory;
+import org.apache.cxf.customer.book.Book;
+import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
+import org.apache.cxf.jaxrs.provider.StreamingResponseProvider;
+import org.apache.cxf.testutil.common.AbstractBusTestServerBase;
+import org.apache.cxf.transport.http.HttpServerEngineSupport;
+
+abstract class AbstractBookServerHttp2 extends AbstractBusTestServerBase {
+    org.apache.cxf.endpoint.Server server;
+
+    private final String port;
+    private final String context;
+    private final String scheme;
+
+    AbstractBookServerHttp2(String port, String context, String scheme) {
+        this.port = port;
+        this.context = context;
+        this.scheme = scheme;
+    }
+
+    protected void run() {
+        SpringBusFactory factory = new SpringBusFactory();
+        Bus bus = factory.createBus(context);
+        bus.setProperty(HttpServerEngineSupport.ENABLE_HTTP2, true);
+        setBus(bus);
+        JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
+        sf.setBus(bus);
+        sf.setResourceClasses(BookStore.class);
+        sf.setProvider(new StreamingResponseProvider<Book>());
+        sf.setResourceProvider(BookStore.class,
+            new SingletonResourceProvider(new BookStore(), true));
+        sf.setAddress(scheme + "://localhost:" + port + "/http2");
+        server = sf.create();
+    }
+
+    public void tearDown() throws Exception {
+        server.stop();
+        server.destroy();
+        server = null;
+    }
+}
diff --git 
a/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/ApacheClientServerHttp2Test.java
 
b/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/ApacheClientServerHttp2Test.java
new file mode 100644
index 0000000000..66c2b66791
--- /dev/null
+++ 
b/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/ApacheClientServerHttp2Test.java
@@ -0,0 +1,48 @@
+/**
+ * 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.
+ */
+
+package org.apache.cxf.systest.hc5.http2;
+
+import org.apache.cxf.jaxrs.model.AbstractResourceInfo;
+
+import org.junit.BeforeClass;
+
+import static org.junit.Assert.assertTrue;
+
+public class ApacheClientServerHttp2Test extends 
AbstractApacheClientServerHttp2Test {
+    private static final String PORT = BookServerHttp2.PORT;
+
+    @BeforeClass
+    public static void startServers() throws Exception {
+        AbstractResourceInfo.clearAllMaps();
+        assertTrue("server did not launch correctly", launchServer(new 
BookServerHttp2()));
+        createStaticBus();
+    }
+
+    
+    @Override
+    protected String getAddress() {
+        return "https://localhost:"; + PORT;
+    }
+    
+    @Override
+    protected String getContext() {
+        return "/http2";
+    }
+}
diff --git 
a/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/ApacheClientServerHttp2cTest.java
 
b/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/ApacheClientServerHttp2cTest.java
new file mode 100644
index 0000000000..5dc91f190f
--- /dev/null
+++ 
b/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/ApacheClientServerHttp2cTest.java
@@ -0,0 +1,47 @@
+/**
+ * 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.
+ */
+
+package org.apache.cxf.systest.hc5.http2;
+
+import org.apache.cxf.jaxrs.model.AbstractResourceInfo;
+
+import org.junit.BeforeClass;
+
+import static org.junit.Assert.assertTrue;
+
+public class ApacheClientServerHttp2cTest extends 
AbstractApacheClientServerHttp2Test {
+    private static final String PORT = BookServerHttp2c.PORT;
+
+    @BeforeClass
+    public static void startServers() throws Exception {
+        AbstractResourceInfo.clearAllMaps();
+        assertTrue("server did not launch correctly", launchServer(new 
BookServerHttp2c()));
+        createStaticBus();
+    }
+
+    @Override
+    protected String getAddress() {
+        return "http://localhost:"; + PORT;
+    }
+    
+    @Override
+    protected String getContext() {
+        return "/http2";
+    }
+}
diff --git 
a/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/Book.java
 
b/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/Book.java
new file mode 100644
index 0000000000..020a584f08
--- /dev/null
+++ 
b/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/Book.java
@@ -0,0 +1,69 @@
+/**
+ * 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.
+ */
+
+package org.apache.cxf.systest.hc5.http2;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
+
+@JsonTypeInfo(use = Id.CLASS, include = As.PROPERTY, property = "class")
+@XmlRootElement(name = "Book")
+public class Book {
+    private String name;
+    private long id;
+
+    public Book() {
+    }
+
+    public Book(String name, long id) {
+        this.name = name;
+        this.id = id;
+    }
+
+    public void setName(String n) {
+        name = n;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setId(long i) {
+        id = i;
+    }
+    public long getId() {
+        return id;
+    }
+
+    @PUT
+    public void cloneState(Book book) {
+        id = book.getId();
+        name = book.getName();
+    }
+
+    @GET
+    public Book retrieveState() {
+        return this;
+    }
+}
diff --git 
a/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/BookServerHttp2.java
 
b/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/BookServerHttp2.java
new file mode 100644
index 0000000000..5a094f5b64
--- /dev/null
+++ 
b/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/BookServerHttp2.java
@@ -0,0 +1,47 @@
+/**
+ * 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.
+ */
+
+package org.apache.cxf.systest.hc5.http2;
+
+public class BookServerHttp2 extends AbstractBookServerHttp2 {
+    public static final String PORT = allocatePort(BookServerHttp2.class);
+
+    org.apache.cxf.endpoint.Server server;
+
+    public BookServerHttp2() {
+        this(PORT);
+    }
+
+    public BookServerHttp2(String port) {
+        super(port, "org/apache/cxf/systest/hc5/http2/server-tls.xml", 
"https");
+    }
+
+    public static void main(String[] args) {
+        try {
+            BookServerHttp2 s = new BookServerHttp2();
+            s.start();
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            System.exit(-1);
+        } finally {
+            System.out.println("done!");
+        }
+    }
+
+}
diff --git 
a/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/BookServerHttp2c.java
 
b/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/BookServerHttp2c.java
new file mode 100644
index 0000000000..6c8d90f951
--- /dev/null
+++ 
b/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/BookServerHttp2c.java
@@ -0,0 +1,45 @@
+/**
+ * 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.
+ */
+
+package org.apache.cxf.systest.hc5.http2;
+
+public class BookServerHttp2c extends AbstractBookServerHttp2 {
+    public static final String PORT = allocatePort(BookServerHttp2c.class);
+
+    public BookServerHttp2c() {
+        this(PORT);
+    }
+
+    public BookServerHttp2c(String port) {
+        super(port, "org/apache/cxf/systest/hc5/http2/server.xml", "http");
+    }
+
+    public static void main(String[] args) {
+        try {
+            BookServerHttp2c s = new BookServerHttp2c();
+            s.start();
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            System.exit(-1);
+        } finally {
+            System.out.println("done!");
+        }
+    }
+
+}
diff --git 
a/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/BookStore.java
 
b/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/BookStore.java
new file mode 100644
index 0000000000..e67dd40e97
--- /dev/null
+++ 
b/systests/transport-hc5/src/test/java/org/apache/cxf/systest/hc5/http2/BookStore.java
@@ -0,0 +1,67 @@
+/**
+ * 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.
+ */
+
+package org.apache.cxf.systest.hc5.http2;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+import org.apache.cxf.jaxrs.ext.StreamingResponse;
+
+@Path("/web/bookstore")
+public class BookStore {
+    private static ExecutorService executor = 
Executors.newSingleThreadExecutor();
+
+    @GET
+    @Path("/booknames")
+    @Produces("text/plain")
+    public byte[] getBookName() {
+        return "CXF in Action".getBytes();
+    }
+
+    @GET
+    @Path("/bookstream")
+    @Produces("application/xml")
+    public StreamingResponse<Book> getBookStream() {
+        return new StreamingResponse<Book>() {
+            public void writeTo(final StreamingResponse.Writer<Book> out) 
throws IOException {
+                out.write(new Book("Book1", 1));
+                executor.execute(new Runnable() {
+                    public void run() {
+                        try {
+                            for (int i = 2; i <= 5; i++) {
+                                Thread.sleep(500);
+                                out.write(new Book("Book" + i, i));
+                                out.getEntityStream().flush();
+                            }
+                        } catch (Exception e) {
+                            e.printStackTrace();
+                        }
+                    }
+                });
+            }
+        };
+    }
+}
+
diff --git 
a/systests/transport-hc5/src/test/resources/org/apache/cxf/systest/hc5/http2/server-tls.xml
 
b/systests/transport-hc5/src/test/resources/org/apache/cxf/systest/hc5/http2/server-tls.xml
new file mode 100644
index 0000000000..134774ee7a
--- /dev/null
+++ 
b/systests/transport-hc5/src/test/resources/org/apache/cxf/systest/hc5/http2/server-tls.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans";
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+    
xmlns:http-jetty="http://cxf.apache.org/transports/http-jetty/configuration";
+    xmlns:sec="http://cxf.apache.org/configuration/security";
+    
xsi:schemaLocation="http://cxf.apache.org/transports/http-jetty/configuration 
+        http://cxf.apache.org/schemas/configuration/http-jetty.xsd 
+        http://www.springframework.org/schema/beans 
+        http://www.springframework.org/schema/beans/spring-beans.xsd
+        http://cxf.apache.org/configuration/security 
+        http://cxf.apache.org/schemas/configuration/security.xsd";>
+    <bean 
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/>
+    <http-jetty:engine-factory bus="cxf">
+        <http-jetty:engine 
port="${testutil.ports.org.apache.cxf.systest.hc5.http2.BookServerHttp2}">
+            <http-jetty:tlsServerParameters>
+                <sec:keyManagers keyPassword="password">
+                    <sec:keyStore type="jks" password="password" 
resource="keys/Bethal.jks"/>
+                </sec:keyManagers>
+                <sec:clientAuthentication want="false" required="false"/>
+            </http-jetty:tlsServerParameters>
+        </http-jetty:engine>
+    </http-jetty:engine-factory>
+</beans>
\ No newline at end of file
diff --git 
a/systests/transport-hc5/src/test/resources/org/apache/cxf/systest/hc5/http2/server.xml
 
b/systests/transport-hc5/src/test/resources/org/apache/cxf/systest/hc5/http2/server.xml
new file mode 100644
index 0000000000..5251ef1562
--- /dev/null
+++ 
b/systests/transport-hc5/src/test/resources/org/apache/cxf/systest/hc5/http2/server.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans";
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+    
xmlns:http-jetty="http://cxf.apache.org/transports/http-jetty/configuration";
+    
xsi:schemaLocation="http://cxf.apache.org/transports/http-jetty/configuration 
+        http://cxf.apache.org/schemas/configuration/http-jetty.xsd 
+        http://www.springframework.org/schema/beans 
+        http://www.springframework.org/schema/beans/spring-beans.xsd";>
+    <bean 
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/>
+    <http-jetty:engine-factory bus="cxf">
+        <http-jetty:engine 
port="${testutil.ports.org.apache.cxf.systest.hc5.http2.BookServerHttp2c}">
+        </http-jetty:engine>
+    </http-jetty:engine-factory>
+</beans>
\ No newline at end of file
diff --git a/systests/transport-netty/pom.xml b/systests/transport-netty/pom.xml
index f099a20080..6427f2ac66 100644
--- a/systests/transport-netty/pom.xml
+++ b/systests/transport-netty/pom.xml
@@ -137,6 +137,11 @@
             <artifactId>cxf-rt-transports-http-netty-server</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-transports-http-netty-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-jdk14</artifactId>
diff --git 
a/systests/transport-netty/src/test/java/org/apache/cxf/systest/http2/netty/AbstractNettyClientServerHttp2Test.java
 
b/systests/transport-netty/src/test/java/org/apache/cxf/systest/http2/netty/AbstractNettyClientServerHttp2Test.java
index b1c2b5c436..c9917426cc 100644
--- 
a/systests/transport-netty/src/test/java/org/apache/cxf/systest/http2/netty/AbstractNettyClientServerHttp2Test.java
+++ 
b/systests/transport-netty/src/test/java/org/apache/cxf/systest/http2/netty/AbstractNettyClientServerHttp2Test.java
@@ -23,103 +23,108 @@ import javax.ws.rs.core.Response;
 
 import org.apache.cxf.configuration.jsse.TLSClientParameters;
 import org.apache.cxf.jaxrs.client.WebClient;
-import org.apache.cxf.systest.http2.netty.Http2TestClient.ClientResponse;
 import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
 import org.apache.cxf.transport.http.HTTPConduit;
+import org.apache.cxf.transport.http.netty.client.NettyHttpConduit;
 import org.apache.cxf.transport.https.InsecureTrustManager;
 
 import org.junit.Test;
 
 import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
 abstract class AbstractNettyClientServerHttp2Test extends 
AbstractBusClientServerTestBase {
     @Test
     public void testBookNotFoundWithHttp2() throws Exception {
-        final Http2TestClient client = new Http2TestClient(isSecure());
-        
-        final ClientResponse response = client
-            .request(getAddress())
+        final WebClient client = createWebClient("/web/bookstore/notFound", 
true);
+        assertThat(WebClient.getConfig(client).getHttpConduit(), 
instanceOf(NettyHttpConduit.class));
+
+        final Response response = client
             .accept("text/plain")
-            .path(getContext() + "/web/bookstore/notFound")
-            .http2()
             .get();
         
-        assertThat(response.getResponseCode(), equalTo(404));
-        assertThat(response.getProtocol(), equalTo("HTTP/2.0"));
+        assertThat(response.getStatus(), equalTo(404));
+        client.close();
     }
     
     @Test
     public void testBookTraceWithHttp2() throws Exception {
-        final Http2TestClient client = new Http2TestClient(isSecure());
-        
-        final ClientResponse response = client
-            .request(getAddress())
+        final WebClient client = createWebClient("/web/bookstore/trace", true);
+        assertThat(WebClient.getConfig(client).getHttpConduit(), 
instanceOf(NettyHttpConduit.class));
+
+        final Response response = client
             .accept("text/plain")
-            .path(getContext() + "/web/bookstore/trace")
-            .http2()
-            .trace();
+            .invoke("TRACE", null);
         
-        assertThat(response.getResponseCode(), equalTo(406));
-        assertThat(response.getProtocol(), equalTo("HTTP/2.0"));
+        assertThat(response.getStatus(), equalTo(406));
+
+        client.close();
     }
     
     @Test
     public void testBookWithHttp2() throws Exception {
-        final Http2TestClient client = new Http2TestClient(isSecure());
+        final WebClient client = createWebClient("/web/bookstore/booknames", 
true);
+        assertThat(WebClient.getConfig(client).getHttpConduit(), 
instanceOf(NettyHttpConduit.class));
         
-        final ClientResponse response = client
-            .request(getAddress())
+        final Response response = client
             .accept("text/plain")
-            .path(getContext() + "/web/bookstore/booknames")
-            .http2()
             .get();
         
-        assertThat(response.getResponseCode(), equalTo(200));
-        assertThat(response.getProtocol(), equalTo("HTTP/2.0"));
-        assertEquals("CXF in Action", response.getBody());
+        assertThat(response.getStatus(), equalTo(200));
+        assertEquals("CXF in Action", response.readEntity(String.class));
+
+        client.close();
     }
 
     @Test
     public void testGetBookStreamHttp2() throws Exception {
-        final Http2TestClient client = new Http2TestClient(isSecure());
+        final WebClient client = createWebClient("/web/bookstore/bookstream", 
true);
+        assertThat(WebClient.getConfig(client).getHttpConduit(), 
instanceOf(NettyHttpConduit.class));
         
-        final ClientResponse response = client
-            .request(getAddress())
+        final Response response = client
             .accept("application/xml")
-            .path(getContext() + "/web/bookstore/bookstream")
-            .http2()
             .get();
 
-        assertThat(response.getResponseCode(), equalTo(200));
-        assertThat(response.getProtocol(), equalTo("HTTP/2.0"));
+        assertThat(response.getStatus(), equalTo(200));
         assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\" 
standalone=\"yes\"?>"
-            + "<Book><id>1</id><name>Book1</name></Book>", response.getBody());
+            + "<Book><id>1</id><name>Book1</name></Book>", 
response.readEntity(String.class));
+
+        client.close();
     }
 
     @Test
     public void testBookWithHttp() throws Exception {
-        final WebClient wc = createWebClient("/web/bookstore/booknames");
-        try (Response resp = wc.get()) {
+        final WebClient client = createWebClient("/web/bookstore/booknames", 
false);
+        
+        try (Response resp = client.get()) {
             assertThat(resp.getStatus(), equalTo(200));
             assertEquals("CXF in Action", resp.readEntity(String.class));
         }
+        
+        client.close();
     }
 
     @Test
     public void testBookTraceWithHttp() throws Exception {
-        final WebClient wc = createWebClient("/web/bookstore/trace");
-        try (Response response = wc.invoke("TRACE", null)) {
+        final WebClient client = createWebClient("/web/bookstore/trace", 
false);
+
+        try (Response response = client.invoke("TRACE", null)) {
             assertThat(response.getStatus(), equalTo(406));
-        }        
+        }
+
+        client.close();
     }
 
-    private WebClient createWebClient(final String path) {
+    private WebClient createWebClient(final String path, final boolean 
enableHttp2) {
         final WebClient wc = WebClient
             .create(getAddress() + getContext() + path)
             .accept("text/plain");
-        
+
+        
WebClient.getConfig(wc).getRequestContext().put(NettyHttpConduit.ENABLE_HTTP2, 
enableHttp2);
+        
WebClient.getConfig(wc).getRequestContext().put(NettyHttpConduit.USE_ASYNC, 
"ALWAYS");
+
         if (isSecure()) {
             final HTTPConduit conduit = 
WebClient.getConfig(wc).getHttpConduit();
             TLSClientParameters params = conduit.getTlsClientParameters();
diff --git 
a/systests/transport-netty/src/test/java/org/apache/cxf/systest/http2/netty/Http2TestClient.java
 
b/systests/transport-netty/src/test/java/org/apache/cxf/systest/http2/netty/Http2TestClient.java
deleted file mode 100644
index b88e610ad0..0000000000
--- 
a/systests/transport-netty/src/test/java/org/apache/cxf/systest/http2/netty/Http2TestClient.java
+++ /dev/null
@@ -1,405 +0,0 @@
-/**
- * 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.
- */
-
-package org.apache.cxf.systest.http2.netty;
-
-import java.io.IOException;
-import java.net.URI;
-import java.util.AbstractMap.SimpleEntry;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.TimeUnit;
-
-import javax.ws.rs.core.MediaType;
-
-import io.netty.bootstrap.Bootstrap;
-import io.netty.buffer.ByteBuf;
-import io.netty.channel.Channel;
-import io.netty.channel.ChannelFuture;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.ChannelInboundHandlerAdapter;
-import io.netty.channel.ChannelInitializer;
-import io.netty.channel.ChannelOption;
-import io.netty.channel.ChannelPromise;
-import io.netty.channel.SimpleChannelInboundHandler;
-import io.netty.channel.nio.NioEventLoopGroup;
-import io.netty.channel.socket.SocketChannel;
-import io.netty.channel.socket.nio.NioSocketChannel;
-import io.netty.handler.codec.http.DefaultFullHttpRequest;
-import io.netty.handler.codec.http.FullHttpRequest;
-import io.netty.handler.codec.http.FullHttpResponse;
-import io.netty.handler.codec.http.HttpClientCodec;
-import io.netty.handler.codec.http.HttpClientUpgradeHandler;
-import io.netty.handler.codec.http.HttpHeaderNames;
-import io.netty.handler.codec.http.HttpMethod;
-import io.netty.handler.codec.http.HttpVersion;
-import io.netty.handler.codec.http2.DefaultHttp2Connection;
-import io.netty.handler.codec.http2.DelegatingDecompressorFrameListener;
-import io.netty.handler.codec.http2.Http2ClientUpgradeCodec;
-import io.netty.handler.codec.http2.Http2Connection;
-import io.netty.handler.codec.http2.Http2ConnectionHandler;
-import io.netty.handler.codec.http2.Http2SecurityUtil;
-import io.netty.handler.codec.http2.Http2Settings;
-import io.netty.handler.codec.http2.HttpConversionUtil;
-import io.netty.handler.codec.http2.HttpToHttp2ConnectionHandlerBuilder;
-import io.netty.handler.codec.http2.InboundHttp2ToHttpAdapterBuilder;
-import io.netty.handler.ssl.ApplicationProtocolConfig;
-import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
-import 
io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
-import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
-import io.netty.handler.ssl.ApplicationProtocolNames;
-import io.netty.handler.ssl.SslContext;
-import io.netty.handler.ssl.SslProvider;
-import io.netty.handler.ssl.SupportedCipherSuiteFilter;
-import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
-import io.netty.util.internal.PlatformDependent;
-
-/**
- * TODO: Use CXF client once https://issues.apache.org/jira/browse/CXF-8606 is 
dones
- */
-public class Http2TestClient implements AutoCloseable {
-    private final SslContext ssl;
-    
-    public Http2TestClient(boolean secure) throws Exception {
-        if (secure) {
-            ssl = SslContext.newClientContext(
-                SslProvider.JDK,
-                null, 
-                InsecureTrustManagerFactory.INSTANCE,
-                Http2SecurityUtil.CIPHERS,
-                SupportedCipherSuiteFilter.INSTANCE,
-                new ApplicationProtocolConfig(
-                        Protocol.ALPN,
-                        SelectorFailureBehavior.FATAL_ALERT,
-                        SelectedListenerFailureBehavior.FATAL_ALERT,
-                        ApplicationProtocolNames.HTTP_2,
-                        ApplicationProtocolNames.HTTP_1_1),
-                0, 0);
-        } else {
-            ssl = null;
-        }
-    }
-    
-    public static class ClientResponse {
-        private String body;
-        private String protocol;
-        private int responseCode;
-        
-        public ClientResponse(int responseCode, String protocol) {
-            this.responseCode = responseCode;
-            this.protocol = protocol;
-        }
-
-        public void setBody(String body) {
-            this.body = body;
-        }
-        
-        public String getBody() {
-            return body;
-        }
-        
-        public void setResponseCode(int rc) {
-            this.responseCode = rc;
-        }
-
-        public int getResponseCode() {
-            return responseCode;
-        }
-
-        public String getProtocol() {
-            return protocol;
-        }
-        
-        public void setProtocol(String protocol) {
-            this.protocol = protocol;
-        }
-    }
-    
-    public class RequestBuilder {
-        private final String address;
-        private String path = "";
-        private String accept = MediaType.WILDCARD;
-        private HttpVersion version = HttpVersion.HTTP_1_1;
-
-        public RequestBuilder(final String address) {
-            this.address = address;
-        }
-        
-        public RequestBuilder path(final String p) {
-            this.path = p;
-            return this;
-        }
-        
-        
-        public RequestBuilder accept(final String a) {
-            this.accept = a;
-            return this;
-        }
-        
-        public RequestBuilder http2() {
-            version = null;
-            return this;
-        }
-        
-        public ClientResponse get() throws Exception {
-            return request(address, path, version, HttpMethod.GET, accept);
-        }
-        
-        public ClientResponse trace() throws Exception {
-            return request(address, path, version, HttpMethod.TRACE, accept);
-        }
-    }
-    
-    public RequestBuilder request(final String address) throws IOException {
-        return new RequestBuilder(address);
-    }
-
-    public ClientResponse request(final String address, final String path, 
-            final HttpVersion version, final HttpMethod method, final String 
accept) 
-                throws Exception {
-
-        final URI uri = URI.create(address);
-                
-        final Http2ClientInitializer initializer = new 
Http2ClientInitializer(Integer.MAX_VALUE);
-        final NioEventLoopGroup worker = new NioEventLoopGroup();
-        
-        final Bootstrap bootstrap = new Bootstrap();
-        bootstrap.group(worker);
-        bootstrap.channel(NioSocketChannel.class);
-        bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
-        bootstrap.remoteAddress(uri.getHost(), uri.getPort());
-        bootstrap.handler(initializer);
-
-        final Channel channel = 
bootstrap.connect().syncUninterruptibly().channel();
-        final HttpResponseHandler responseHandler = 
initializer.getResponseHandler();
-        final Http2SettingsHandler http2SettingsHandler = 
initializer.getSettingsHandler();
-
-        try {
-            final FullHttpRequest request = new 
DefaultFullHttpRequest(HttpVersion.HTTP_1_1, method, path);
-            request.headers().add(HttpHeaderNames.HOST, uri.getHost());
-            request.headers().add(HttpHeaderNames.ACCEPT, accept);
-            
request.headers().add(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), 
uri.getScheme());
-
-            http2SettingsHandler.awaitSettings(5, TimeUnit.SECONDS);
-            responseHandler.put(3, channel.write(request), 
channel.newPromise());
-            
-            channel.flush();
-            responseHandler.awaitResponses(15, TimeUnit.SECONDS);
-        } finally {
-            channel.close().awaitUninterruptibly();
-            worker.shutdownGracefully();
-        }
-        
-        
-        final List<ClientResponse> responses = responseHandler.responses();
-        if (responses.size() != 1) {
-            throw new IllegalStateException("Expected exactly one response, 
but got 0 or more");
-        }
-        
-        return responses.get(0);
-    }
-    
-    @Override
-    public void close() throws Exception {
-    }
-    
-    private class Http2SettingsHandler extends 
SimpleChannelInboundHandler<Http2Settings> {
-        private ChannelPromise promise;
-
-        Http2SettingsHandler(ChannelPromise promise) {
-            this.promise = promise;
-        }
-
-        /**
-         * Wait for this handler to be added after the upgrade to HTTP/2, and 
for initial preface
-         * handshake to complete.
-         */
-        void awaitSettings(long timeout, TimeUnit unit) throws Exception {
-            if (!promise.awaitUninterruptibly(timeout, unit)) {
-                throw new IllegalStateException("Timed out waiting for 
settings");
-            }
-            if (!promise.isSuccess()) {
-                throw new RuntimeException(promise.cause());
-            }
-        }
-        
-        @Override
-        protected void channelRead0(ChannelHandlerContext ctx, Http2Settings 
msg) throws Exception {
-            promise.setSuccess();
-            ctx.pipeline().remove(this);
-        }
-    }
-    
-    private class HttpResponseHandler extends 
SimpleChannelInboundHandler<FullHttpResponse> {
-        private final Map<Integer, Entry<ChannelFuture, ChannelPromise>> 
streamidPromiseMap;
-        private final List<ClientResponse> responses = new 
CopyOnWriteArrayList<>(); 
-        
-        HttpResponseHandler() {
-            streamidPromiseMap = PlatformDependent.newConcurrentHashMap();
-        }
-
-        Entry<ChannelFuture, ChannelPromise> put(int streamId, ChannelFuture 
writeFuture, ChannelPromise promise) {
-            return streamidPromiseMap.put(streamId, new 
SimpleEntry<>(writeFuture, promise));
-        }
-
-        void awaitResponses(long timeout, TimeUnit unit) {
-            final Iterator<Entry<Integer, Entry<ChannelFuture, 
ChannelPromise>>> itr = streamidPromiseMap
-                .entrySet()
-                .iterator();
-            
-            while (itr.hasNext()) {
-                final Entry<Integer, Entry<ChannelFuture, ChannelPromise>> 
entry = itr.next();
-
-                final ChannelFuture writeFuture = entry.getValue().getKey();
-                if (!writeFuture.awaitUninterruptibly(timeout, unit)) {
-                    throw new IllegalStateException("Timed out waiting to 
write for stream id " + entry.getKey());
-                }
-                
-                if (!writeFuture.isSuccess()) {
-                    throw new RuntimeException(writeFuture.cause());
-                }
-                
-                final ChannelPromise promise = entry.getValue().getValue();
-                if (!promise.awaitUninterruptibly(timeout, unit)) {
-                    throw new IllegalStateException("Timed out waiting for 
response on stream id " + entry.getKey());
-                }
-                
-                if (!promise.isSuccess()) {
-                    throw new RuntimeException(promise.cause());
-                }
-
-                itr.remove();
-            }
-        }
-        
-        List<ClientResponse> responses() {
-            return responses;
-        }
-
-        @Override
-        protected void channelRead0(ChannelHandlerContext ctx, 
FullHttpResponse msg) throws Exception {
-            Integer streamId = 
msg.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text());
-            if (streamId == null) {
-                System.err.println("HttpResponseHandler unexpected message 
received: " + msg);
-                return;
-            }
-
-            final Entry<ChannelFuture, ChannelPromise> entry = 
streamidPromiseMap.get(streamId);
-            if (entry == null) {
-                System.err.println("Message received for unknown stream id " + 
streamId);
-            } else {
-                final ByteBuf content = msg.content();
-                final ClientResponse response = new 
ClientResponse(msg.status().code(), "HTTP/2.0");
-                
-                if (content.isReadable()) {
-                    int contentLength = content.readableBytes();
-                    byte[] arr = new byte[contentLength];
-                    content.readBytes(arr);
-                    response.setBody(new String(arr));
-                }
-
-                responses.add(response);
-                entry.getValue().setSuccess();
-            }
-        }
-    }
-    
-    private class Http2ClientInitializer extends 
ChannelInitializer<SocketChannel> {
-        private final int maxContentLength;
-        private HttpResponseHandler responseHandler;
-        private Http2SettingsHandler settingsHandler;
-        private Http2ConnectionHandler connectionHandler;
-
-        Http2ClientInitializer(int maxContentLength) {
-            this.maxContentLength = maxContentLength;
-        }
-
-        @Override
-        public void initChannel(SocketChannel ch) throws Exception {
-            final Http2Connection connection = new 
DefaultHttp2Connection(false);
-
-            responseHandler = new HttpResponseHandler();
-            settingsHandler = new Http2SettingsHandler(ch.newPromise());
-            
-            connectionHandler = new HttpToHttp2ConnectionHandlerBuilder()
-                .connection(connection)
-                .frameListener(new 
DelegatingDecompressorFrameListener(connection,
-                    new InboundHttp2ToHttpAdapterBuilder(connection)
-                        .maxContentLength(maxContentLength)
-                        .propagateSettings(true)
-                        .build()))
-                .build();
-
-            if (ssl != null) {
-                ch.pipeline().addLast(ssl.newHandler(ch.alloc()));
-                ch.pipeline().addLast(connectionHandler);
-                ch.pipeline().addLast(settingsHandler);
-                ch.pipeline().addLast(responseHandler);
-            } else {
-                final HttpClientCodec sourceCodec = new HttpClientCodec();
-                final Http2ClientUpgradeCodec upgradeCodec = new 
Http2ClientUpgradeCodec(connectionHandler);
-                final HttpClientUpgradeHandler upgradeHandler = new 
HttpClientUpgradeHandler(sourceCodec, 
-                    upgradeCodec, 65536);
-
-                ch.pipeline().addLast(sourceCodec);
-                ch.pipeline().addLast(upgradeHandler);
-                ch.pipeline().addLast(new 
UpgradeRequestHandler(settingsHandler, responseHandler));
-            }
-        }
-        
-        HttpResponseHandler getResponseHandler() {
-            return responseHandler;
-        }
-        
-        Http2SettingsHandler getSettingsHandler() {
-            return settingsHandler;
-        }
-    }
-    
-    /**
-     * A handler that triggers the cleartext upgrade to HTTP/2 by sending an 
-     * initial HTTP request.
-     */
-    private class UpgradeRequestHandler extends ChannelInboundHandlerAdapter {
-        private final Http2SettingsHandler settingsHandler;
-        private final HttpResponseHandler responseHandler;
-
-        UpgradeRequestHandler(final Http2SettingsHandler settingsHandler, 
final HttpResponseHandler responseHandler) {
-            this.settingsHandler = settingsHandler;
-            this.responseHandler = responseHandler;
-        }
-        
-        @Override
-        public void channelActive(ChannelHandlerContext ctx) throws Exception {
-            final FullHttpRequest request = new 
DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/");
-            request.headers().add(HttpHeaderNames.HOST, "localhost");
-            
request.headers().add(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), 
"http");
-
-            ctx.writeAndFlush(request);
-            ctx.fireChannelActive();
-            
-            ctx.pipeline().remove(this);
-            ctx.pipeline().addLast(settingsHandler);
-            ctx.pipeline().addLast(responseHandler);
-        }
-    }
-}

Reply via email to