You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by re...@apache.org on 2023/05/07 17:31:22 UTC
[cxf] 03/06: CXF-8606: Introduce HTTP/2 Transport: client-side support (#1017)
This is an automated email from the ASF dual-hosted git repository.
reta pushed a commit to branch 3.4.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 589453d9c721b6fffa02970a4c9e70e982c5a894
Author: Andriy Redko <dr...@gmail.com>
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
(cherry picked from commit 40f09e6a0db63c8cddbd937bc4e26c3dd7ab7425)
# Conflicts:
# rt/transports/http-hc5/src/main/java/org/apache/cxf/transport/http/asyncclient/hc5/AsyncHTTPConduit.java
# rt/transports/http-netty/netty-client/src/main/java/org/apache/cxf/transport/http/netty/client/NettyHttpConduit.java
# systests/transport-netty/src/test/java/org/apache/cxf/systest/http2/netty/AbstractNettyClientServerHttp2Test.java
(cherry picked from commit 80d4da129dfdf82e3c7c15e6c0548fc5ad78ee6c)
---
.../http/asyncclient/hc5/AsyncHTTPConduit.java | 16 +
.../asyncclient/hc5/AsyncHTTPConduitFactory.java | 5 +
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} | 111 +++---
.../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 | 89 ++---
.../cxf/systest/http2/netty/Http2TestClient.java | 405 ---------------------
22 files changed, 860 insertions(+), 523 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 ec15b42b40..6d1bb0e3c5 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 URLConnectionHTTPConduit {
+ /**
+ * 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 URLConnectionHTTPConduit {
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 URLConnectionHTTPConduit {
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..a2df8b61b8 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
@@ -54,6 +54,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;
@@ -372,6 +373,10 @@ public class AsyncHTTPConduitFactory implements HTTPConduitFactory {
}
});
+ if (Boolean.FALSE.equals(clientPolicy.isEnableHttp2())) {
+ httpAsyncClientBuilder.setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1);
+ }
+
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 c435b04621..8d2538931b 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 6110281af1..5e935623c9 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 de94af8ddf..c8ce0064b8 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 URLConnectionHTTPConduit 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 URLConnectionHTTPConduit implements BusLif
// 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 URLConnectionHTTPConduit implements BusLif
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 URLConnectionHTTPConduit implements BusLif
}
+ 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 URLConnectionHTTPConduit implements BusLif
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 URLConnectionHTTPConduit implements BusLif
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 URLConnectionHTTPConduit implements BusLif
return (HttpContent) getHttpResponse();
}
-
protected Channel getChannel() throws IOException {
syncLock.lock();
try {
@@ -371,22 +387,25 @@ public class NettyHttpConduit extends URLConnectionHTTPConduit implements BusLif
}
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 URLConnectionHTTPConduit implements BusLif
connFuture.addListener(listener);
-
// setup the CxfResponseCallBack
CxfResponseCallBack callBack = new CxfResponseCallBack() {
@Override
@@ -440,8 +458,15 @@ public class NettyHttpConduit extends URLConnectionHTTPConduit implements BusLif
@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 cfc33246c4..7de1af9c92 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
@@ -152,7 +152,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 0109368d85..766d16accd 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
@@ -390,6 +390,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 b35c916049..ea3f231cda 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.jetty9.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 56%
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 5945b1fb8f..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;
-import static org.junit.Assert.assertThat;
-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 486f72bd54..6a03d3baa2 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 5945b1fb8f..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;
-import static org.junit.Assert.assertThat;
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);
- }
- }
-}