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); - } - } -}
