From 6228a7361350df0c1ec99ebfd36034438512f068 Mon Sep 17 00:00:00 2001 From: Oleg Kalnichevski Date: Mon, 13 Nov 2017 10:50:27 +0100 Subject: [PATCH] * HTTP/2 multiplexing HttpAsyncClient implementation * Restructured integration tests to reduce test duplication --- ... => AbstractHttp1IntegrationTestBase.java} | 31 +- ...bstractHttpAsyncClientAuthentication.java} | 113 +-- ...=> AbstractHttpAsyncFundamentalsTest.java} | 122 +-- ...va => AbstractHttpAsyncRedirectsTest.java} | 101 +- .../async/AbstractIntegrationTestBase.java | 114 +++ ...tBase.java => AbstractServerTestBase.java} | 30 +- .../client5/testing/async/TestHttp1Async.java | 198 ++++ .../async/TestHttp1AsyncRedirects.java | 254 +++++ ...TestHttp1AsyncStatefulConnManagement.java} | 60 +- .../async/TestHttp1ClientAuthentication.java | 180 ++++ .../client5/testing/async/TestHttp2Async.java | 88 ++ .../testing/async/TestHttp2AsyncMinimal.java | 171 +--- .../testing/async/TestHttp2AsyncRedirect.java | 88 ++ .../async/TestHttp2ClientAuthentication.java | 97 ++ .../testing/async/TestHttpAsyncMinimal.java | 176 +--- .../impl/async/Http2AsyncClientBuilder.java | 885 ++++++++++++++++++ .../Http2AsyncClientEventHandlerFactory.java | 194 ++++ .../impl/async/Http2AsyncMainClientExec.java | 170 ++++ .../impl/async/HttpAsyncClientBuilder.java | 2 +- .../HttpAsyncClientEventHandlerFactory.java | 2 +- .../http/impl/async/HttpAsyncClients.java | 32 +- ...Exec.java => HttpAsyncMainClientExec.java} | 4 +- .../InternalAbstractHttpAsyncClient.java | 308 ++++++ .../impl/async/InternalHttp2AsyncClient.java | 88 ++ .../async/InternalHttp2AsyncExecRuntime.java | 245 +++++ .../impl/async/InternalHttpAsyncClient.java | 254 +---- ...java => InternalHttpAsyncExecRuntime.java} | 8 +- 27 files changed, 3131 insertions(+), 884 deletions(-) rename httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/{IntegrationTestBase.java => AbstractHttp1IntegrationTestBase.java} (78%) rename httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/{TestClientAuthentication.java => AbstractHttpAsyncClientAuthentication.java} (86%) rename httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/{TestHttpAsync.java => AbstractHttpAsyncFundamentalsTest.java} (51%) rename httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/{TestAsyncRedirects.java => AbstractHttpAsyncRedirectsTest.java} (90%) create mode 100644 httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractIntegrationTestBase.java rename httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/{LocalAsyncServerTestBase.java => AbstractServerTestBase.java} (75%) create mode 100644 httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1Async.java create mode 100644 httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncRedirects.java rename httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/{TestAsyncStatefulConnManagement.java => TestHttp1AsyncStatefulConnManagement.java} (82%) create mode 100644 httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1ClientAuthentication.java create mode 100644 httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2Async.java create mode 100644 httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2AsyncRedirect.java create mode 100644 httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2ClientAuthentication.java create mode 100644 httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncClientBuilder.java create mode 100644 httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncClientEventHandlerFactory.java create mode 100644 httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncMainClientExec.java rename httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/{AsyncMainClientExec.java => HttpAsyncMainClientExec.java} (98%) create mode 100644 httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalAbstractHttpAsyncClient.java create mode 100644 httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttp2AsyncClient.java create mode 100644 httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttp2AsyncExecRuntime.java rename httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/{AsyncExecRuntimeImpl.java => InternalHttpAsyncExecRuntime.java} (97%) diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/IntegrationTestBase.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttp1IntegrationTestBase.java similarity index 78% rename from httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/IntegrationTestBase.java rename to httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttp1IntegrationTestBase.java index 31802278f..18dd89e09 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/IntegrationTestBase.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttp1IntegrationTestBase.java @@ -33,6 +33,10 @@ import java.util.concurrent.Future; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; +import org.apache.hc.client5.http.ssl.H2TlsStrategy; +import org.apache.hc.client5.testing.SSLTestContexts; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.URIScheme; @@ -45,19 +49,40 @@ import org.apache.hc.core5.reactor.ListenerEndpoint; import org.junit.Rule; import org.junit.rules.ExternalResource; -public abstract class IntegrationTestBase extends LocalAsyncServerTestBase { +public abstract class AbstractHttp1IntegrationTestBase extends AbstractServerTestBase { - public IntegrationTestBase(final URIScheme scheme) { + public AbstractHttp1IntegrationTestBase(final URIScheme scheme) { super(scheme); } - public IntegrationTestBase() { + public AbstractHttp1IntegrationTestBase() { super(URIScheme.HTTP); } protected HttpAsyncClientBuilder clientBuilder; + protected PoolingAsyncClientConnectionManager connManager; protected CloseableHttpAsyncClient httpclient; + @Rule + public ExternalResource connManagerResource = new ExternalResource() { + + @Override + protected void before() throws Throwable { + connManager = PoolingAsyncClientConnectionManagerBuilder.create() + .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext())) + .build(); + } + + @Override + protected void after() { + if (connManager != null) { + connManager.close(); + connManager = null; + } + } + + }; + @Rule public ExternalResource clientResource = new ExternalResource() { diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestClientAuthentication.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncClientAuthentication.java similarity index 86% rename from httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestClientAuthentication.java rename to httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncClientAuthentication.java index 7841420b9..7dc80986b 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestClientAuthentication.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncClientAuthentication.java @@ -27,12 +27,12 @@ package org.apache.hc.client5.testing.async; import java.util.Arrays; -import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicLong; +import org.apache.hc.client5.http.AuthenticationStrategy; import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; import org.apache.hc.client5.http.auth.AuthChallenge; @@ -45,6 +45,7 @@ import org.apache.hc.client5.http.auth.CredentialsStore; import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; import org.apache.hc.client5.http.impl.auth.BasicScheme; import org.apache.hc.client5.http.protocol.HttpClientContext; @@ -53,14 +54,15 @@ import org.apache.hc.client5.testing.auth.Authenticator; import org.apache.hc.core5.function.Decorator; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ContentType; -import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.config.H1Config; +import org.apache.hc.core5.http.config.Lookup; import org.apache.hc.core5.http.config.Registry; import org.apache.hc.core5.http.config.RegistryBuilder; import org.apache.hc.core5.http.impl.HttpProcessors; @@ -68,42 +70,52 @@ import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; +import org.apache.hc.core5.http2.config.H2Config; +import org.apache.hc.core5.http2.impl.Http2Processors; import org.apache.hc.core5.net.URIAuthority; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -@RunWith(Parameterized.class) -public class TestClientAuthentication extends IntegrationTestBase { +public abstract class AbstractHttpAsyncClientAuthentication extends AbstractIntegrationTestBase { - @Parameterized.Parameters(name = "{0}") - public static Collection protocols() { - return Arrays.asList(new Object[][]{ - {URIScheme.HTTP}, - {URIScheme.HTTPS}, - }); - } + protected final HttpVersion protocolVersion; - public TestClientAuthentication(final URIScheme scheme) { + public AbstractHttpAsyncClientAuthentication(final URIScheme scheme, final HttpVersion protocolVersion) { super(scheme); + this.protocolVersion = protocolVersion; } @Override - public HttpHost start() throws Exception { - return super.start( - HttpProcessors.server(), - new Decorator() { + public final HttpHost start() throws Exception { + return start(new Decorator() { - @Override - public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler requestHandler) { - return new AuthenticatingAsyncDecorator(requestHandler, new BasicTestAuthenticator("test:test", "test realm")); - } + @Override + public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler requestHandler) { + return new AuthenticatingAsyncDecorator(requestHandler, new BasicTestAuthenticator("test:test", "test realm")); + } - }, - H1Config.DEFAULT); + }); } + public final HttpHost start( + final Decorator exchangeHandlerDecorator) throws Exception { + if (protocolVersion.greaterEquals(HttpVersion.HTTP_2_0)) { + return super.start( + Http2Processors.server(), + exchangeHandlerDecorator, + H2Config.DEFAULT); + } else { + return super.start( + HttpProcessors.server(), + exchangeHandlerDecorator, + H1Config.DEFAULT); + } + } + + abstract void setDefaultAuthSchemeRegistry(Lookup authSchemeRegistry); + + abstract void setTargetAuthenticationStrategy(AuthenticationStrategy targetAuthStrategy); + static class TestCredentialsProvider implements CredentialsStore { private final Credentials creds; @@ -243,49 +255,6 @@ public class TestClientAuthentication extends IntegrationTestBase { Assert.assertEquals("test realm", authscope.getRealm()); } - @Test - public void testBasicAuthenticationSuccessNonPersistentConnection() throws Exception { - server.register("*", new Supplier() { - - @Override - public AsyncServerExchangeHandler get() { - return new AsyncEchoHandler(); - } - - }); - final HttpHost target = start( - HttpProcessors.server(), - new Decorator() { - - @Override - public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) { - return new AuthenticatingAsyncDecorator(exchangeHandler, new BasicTestAuthenticator("test:test", "test realm")) { - - @Override - protected void customizeUnauthorizedResponse(final HttpResponse unauthorized) { - unauthorized.addHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE); - } - }; - } - - }, - H1Config.DEFAULT); - - final TestCredentialsProvider credsProvider = new TestCredentialsProvider( - new UsernamePasswordCredentials("test", "test".toCharArray())); - final HttpClientContext context = HttpClientContext.create(); - context.setCredentialsProvider(credsProvider); - - final Future future = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null); - final HttpResponse response = future.get(); - - Assert.assertNotNull(response); - Assert.assertEquals(HttpStatus.SC_OK, response.getCode()); - final AuthScope authscope = credsProvider.getAuthScope(); - Assert.assertNotNull(authscope); - Assert.assertEquals("test realm", authscope.getRealm()); - } - @Test public void testBasicAuthenticationExpectationFailure() throws Exception { server.register("*", new Supplier() { @@ -355,7 +324,7 @@ public class TestClientAuthentication extends IntegrationTestBase { }); final AtomicLong count = new AtomicLong(0); - this.clientBuilder.setTargetAuthenticationStrategy(new DefaultAuthenticationStrategy() { + setTargetAuthenticationStrategy(new DefaultAuthenticationStrategy() { @Override public List select( @@ -497,7 +466,7 @@ public class TestClientAuthentication extends IntegrationTestBase { }) .build(); - this.clientBuilder.setDefaultAuthSchemeRegistry(authSchemeRegistry); + setDefaultAuthSchemeRegistry(authSchemeRegistry); final Authenticator authenticator = new BasicTestAuthenticator("test:test", "test realm") { @@ -519,7 +488,6 @@ public class TestClientAuthentication extends IntegrationTestBase { }; final HttpHost target = start( - HttpProcessors.server(), new Decorator() { @Override @@ -535,7 +503,7 @@ public class TestClientAuthentication extends IntegrationTestBase { }; } - }, H1Config.DEFAULT); + }); final RequestConfig config = RequestConfig.custom() .setTargetPreferredAuthSchemes(Arrays.asList("MyBasic")) @@ -564,7 +532,6 @@ public class TestClientAuthentication extends IntegrationTestBase { }); final HttpHost target = start( - HttpProcessors.server(), new Decorator() { @Override @@ -579,7 +546,7 @@ public class TestClientAuthentication extends IntegrationTestBase { }; } - }, H1Config.DEFAULT); + }); final TestCredentialsProvider credsProvider = new TestCredentialsProvider( new UsernamePasswordCredentials("test", "test".toCharArray())); diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsync.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncFundamentalsTest.java similarity index 51% rename from httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsync.java rename to httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncFundamentalsTest.java index 5f32a6f85..73c74098a 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsync.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncFundamentalsTest.java @@ -26,8 +26,6 @@ */ package org.apache.hc.client5.testing.async; -import java.util.Arrays; -import java.util.Collection; import java.util.LinkedList; import java.util.Queue; import java.util.Random; @@ -37,11 +35,8 @@ import org.apache.hc.client5.http.async.methods.AsyncRequestBuilder; import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; -import org.apache.hc.client5.http.impl.async.HttpAsyncClients; import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.core5.http.ContentType; -import org.apache.hc.core5.http.HeaderElements; -import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.Message; @@ -51,21 +46,10 @@ import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityConsumer; import org.hamcrest.CoreMatchers; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -@RunWith(Parameterized.class) -public class TestHttpAsync extends IntegrationTestBase { +public abstract class AbstractHttpAsyncFundamentalsTest extends AbstractIntegrationTestBase { - @Parameterized.Parameters(name = "{0}") - public static Collection protocols() { - return Arrays.asList(new Object[][]{ - { URIScheme.HTTP }, - { URIScheme.HTTPS }, - }); - } - - public TestHttpAsync(final URIScheme scheme) { + public AbstractHttpAsyncFundamentalsTest(final URIScheme scheme) { super(scheme); } @@ -98,22 +82,6 @@ public class TestHttpAsync extends IntegrationTestBase { } } - @Test - public void testSequenctialGetRequestsCloseConnection() throws Exception { - final HttpHost target = start(); - for (int i = 0; i < 3; i++) { - final SimpleHttpRequest get = SimpleHttpRequest.get(target, "/random/2048"); - get.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE); - final Future future = httpclient.execute(get, null); - final SimpleHttpResponse response = future.get(); - Assert.assertThat(response, CoreMatchers.notNullValue()); - Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200)); - final String body = response.getBodyText(); - Assert.assertThat(body, CoreMatchers.notNullValue()); - Assert.assertThat(body.length(), CoreMatchers.equalTo(2048)); - } - } - @Test public void testSequenctialPostRequests() throws Exception { final HttpHost target = start(); @@ -136,7 +104,7 @@ public class TestHttpAsync extends IntegrationTestBase { } @Test - public void testConcurrentPostsOverMultipleConnections() throws Exception { + public void testConcurrentPostRequests() throws Exception { final HttpHost target = start(); final byte[] b1 = new byte[1024]; final Random rnd = new Random(System.currentTimeMillis()); @@ -144,9 +112,6 @@ public class TestHttpAsync extends IntegrationTestBase { final int reqCount = 20; - connManager.setDefaultMaxPerRoute(reqCount); - connManager.setMaxTotal(100); - final Queue>> queue = new LinkedList<>(); for (int i = 0; i < reqCount; i++) { final Future> future = httpclient.execute( @@ -168,85 +133,4 @@ public class TestHttpAsync extends IntegrationTestBase { } } - @Test - public void testConcurrentPostsOverSingleConnection() throws Exception { - final HttpHost target = start(); - final byte[] b1 = new byte[1024]; - final Random rnd = new Random(System.currentTimeMillis()); - rnd.nextBytes(b1); - - final int reqCount = 20; - - connManager.setDefaultMaxPerRoute(1); - connManager.setMaxTotal(100); - - final Queue>> queue = new LinkedList<>(); - for (int i = 0; i < reqCount; i++) { - final Future> future = httpclient.execute( - AsyncRequestBuilder.post(target, "/echo/") - .setEntity(b1, ContentType.APPLICATION_OCTET_STREAM) - .build(), - new BasicResponseConsumer<>(new BasicAsyncEntityConsumer()), HttpClientContext.create(), null); - queue.add(future); - } - - while (!queue.isEmpty()) { - final Future> future = queue.remove(); - final Message responseMessage = future.get(); - Assert.assertThat(responseMessage, CoreMatchers.notNullValue()); - final HttpResponse response = responseMessage.getHead(); - Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200)); - final byte[] b2 = responseMessage.getBody(); - Assert.assertThat(b1, CoreMatchers.equalTo(b2)); - } - } - - @Test - public void testSharedPool() throws Exception { - final HttpHost target = start(); - final Future future1 = httpclient.execute( - SimpleHttpRequest.get(target, "/random/2048"), null); - final SimpleHttpResponse response1 = future1.get(); - Assert.assertThat(response1, CoreMatchers.notNullValue()); - Assert.assertThat(response1.getCode(), CoreMatchers.equalTo(200)); - final String body1 = response1.getBodyText(); - Assert.assertThat(body1, CoreMatchers.notNullValue()); - Assert.assertThat(body1.length(), CoreMatchers.equalTo(2048)); - - - try (final CloseableHttpAsyncClient httpclient2 = HttpAsyncClients.custom() - .setConnectionManager(connManager) - .setConnectionManagerShared(true) - .build()) { - httpclient2.start(); - final Future future2 = httpclient2.execute( - SimpleHttpRequest.get(target, "/random/2048"), null); - final SimpleHttpResponse response2 = future2.get(); - Assert.assertThat(response2, CoreMatchers.notNullValue()); - Assert.assertThat(response2.getCode(), CoreMatchers.equalTo(200)); - final String body2 = response2.getBodyText(); - Assert.assertThat(body2, CoreMatchers.notNullValue()); - Assert.assertThat(body2.length(), CoreMatchers.equalTo(2048)); - } - - final Future future3 = httpclient.execute( - SimpleHttpRequest.get(target, "/random/2048"), null); - final SimpleHttpResponse response3 = future3.get(); - Assert.assertThat(response3, CoreMatchers.notNullValue()); - Assert.assertThat(response3.getCode(), CoreMatchers.equalTo(200)); - final String body3 = response3.getBodyText(); - Assert.assertThat(body3, CoreMatchers.notNullValue()); - Assert.assertThat(body3.length(), CoreMatchers.equalTo(2048)); - } - - @Test - public void testBadRequest() throws Exception { - final HttpHost target = start(); - final Future future = httpclient.execute( - SimpleHttpRequest.get(target, "/random/boom"), null); - final SimpleHttpResponse response = future.get(); - Assert.assertThat(response, CoreMatchers.notNullValue()); - Assert.assertThat(response.getCode(), CoreMatchers.equalTo(400)); - } - } \ No newline at end of file diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncRedirects.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncRedirectsTest.java similarity index 90% rename from httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncRedirects.java rename to httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncRedirectsTest.java index 83c429eb4..4649b5042 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncRedirects.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncRedirectsTest.java @@ -29,10 +29,6 @@ package org.apache.hc.client5.testing.async; import java.net.InetSocketAddress; import java.net.URI; import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutionException; @@ -45,6 +41,7 @@ import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.cookie.BasicCookieStore; import org.apache.hc.client5.http.cookie.CookieStore; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; import org.apache.hc.client5.http.impl.cookie.BasicClientCookie; import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.client5.testing.SSLTestContexts; @@ -52,7 +49,6 @@ import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; -import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; @@ -69,40 +65,20 @@ import org.apache.hc.core5.testing.nio.Http2TestServer; import org.apache.hc.core5.util.TimeValue; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -/** - * Redirection test cases. - */ -@RunWith(Parameterized.class) -public class TestAsyncRedirects extends IntegrationTestBase { +public abstract class AbstractHttpAsyncRedirectsTest extends AbstractIntegrationTestBase { - @Parameterized.Parameters(name = "{0}") - public static Collection protocols() { - return Arrays.asList(new Object[][]{ - {URIScheme.HTTP}, - {URIScheme.HTTPS}, - }); - } - - public TestAsyncRedirects(final URIScheme scheme) { + public AbstractHttpAsyncRedirectsTest(final URIScheme scheme) { super(scheme); } static class BasicRedirectService extends AbstractSimpleServerExchangeHandler { private final int statuscode; - private final boolean keepAlive; - - public BasicRedirectService(final int statuscode, final boolean keepAlive) { - super(); - this.statuscode = statuscode; - this.keepAlive = keepAlive; - } public BasicRedirectService(final int statuscode) { - this(statuscode, true); + super(); + this.statuscode = statuscode; } @Override @@ -115,9 +91,6 @@ public class TestAsyncRedirects extends IntegrationTestBase { final SimpleHttpResponse response = new SimpleHttpResponse(statuscode); response.addHeader(new BasicHeader("Location", new URIBuilder(requestURI).setPath("/newlocation/").build())); - if (!keepAlive) { - response.addHeader(new BasicHeader("Connection", "close")); - } return response; } else if (path.equals("/newlocation/")) { final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_OK); @@ -220,7 +193,7 @@ public class TestAsyncRedirects extends IntegrationTestBase { @Override public AsyncServerExchangeHandler get() { - return new BasicRedirectService(HttpStatus.SC_MULTIPLE_CHOICES, false); + return new BasicRedirectService(HttpStatus.SC_MULTIPLE_CHOICES); } }); @@ -239,37 +212,12 @@ public class TestAsyncRedirects extends IntegrationTestBase { } @Test - public void testBasicRedirect301KeepAlive() throws Exception { + public void testBasicRedirect301() throws Exception { server.register("*", new Supplier() { @Override public AsyncServerExchangeHandler get() { - return new BasicRedirectService(HttpStatus.SC_MOVED_PERMANENTLY, true); - } - - }); - - final HttpHost target = start(); - final HttpClientContext context = HttpClientContext.create(); - final Future future = httpclient.execute( - SimpleHttpRequest.get(target, "/oldlocation/"), context, null); - final HttpResponse response = future.get(); - Assert.assertNotNull(response); - - final HttpRequest request = context.getRequest(); - - Assert.assertEquals(HttpStatus.SC_OK, response.getCode()); - Assert.assertEquals("/newlocation/", request.getRequestUri()); - Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme())); - } - - @Test - public void testBasicRedirect301NoKeepAlive() throws Exception { - server.register("*", new Supplier() { - - @Override - public AsyncServerExchangeHandler get() { - return new BasicRedirectService(HttpStatus.SC_MOVED_PERMANENTLY, false); + return new BasicRedirectService(HttpStatus.SC_MOVED_PERMANENTLY); } }); @@ -681,39 +629,6 @@ public class TestAsyncRedirects extends IntegrationTestBase { Assert.assertEquals("There can only be one (cookie)", 1, headers.length); } - @Test - public void testDefaultHeadersRedirect() throws Exception { - server.register("*", new Supplier() { - - @Override - public AsyncServerExchangeHandler get() { - return new BasicRedirectService(HttpStatus.SC_MOVED_TEMPORARILY); - } - - }); - - final List
defaultHeaders = new ArrayList<>(1); - defaultHeaders.add(new BasicHeader(HttpHeaders.USER_AGENT, "my-test-client")); - clientBuilder.setDefaultHeaders(defaultHeaders); - - final HttpHost target = start(); - - final HttpClientContext context = HttpClientContext.create(); - - final Future future = httpclient.execute( - SimpleHttpRequest.get(target, "/oldlocation/"), context, null); - final HttpResponse response = future.get(); - Assert.assertNotNull(response); - - final HttpRequest request = context.getRequest(); - - Assert.assertEquals(HttpStatus.SC_OK, response.getCode()); - Assert.assertEquals("/newlocation/", request.getRequestUri()); - - final Header header = request.getFirstHeader(HttpHeaders.USER_AGENT); - Assert.assertEquals("my-test-client", header.getValue()); - } - static class CrossSiteRedirectService extends AbstractSimpleServerExchangeHandler { private final HttpHost host; diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractIntegrationTestBase.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractIntegrationTestBase.java new file mode 100644 index 000000000..c19378561 --- /dev/null +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractIntegrationTestBase.java @@ -0,0 +1,114 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.hc.client5.testing.async; + +import java.net.InetSocketAddress; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.core5.function.Decorator; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.URIScheme; +import org.apache.hc.core5.http.config.H1Config; +import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; +import org.apache.hc.core5.http.protocol.HttpProcessor; +import org.apache.hc.core5.http2.config.H2Config; +import org.apache.hc.core5.io.ShutdownType; +import org.apache.hc.core5.reactor.ListenerEndpoint; +import org.junit.Rule; +import org.junit.rules.ExternalResource; + +public abstract class AbstractIntegrationTestBase extends AbstractServerTestBase { + + public AbstractIntegrationTestBase(final URIScheme scheme) { + super(scheme); + } + + public AbstractIntegrationTestBase() { + super(URIScheme.HTTP); + } + + protected T httpclient; + + protected abstract T createClient() throws Exception; + + @Rule + public ExternalResource clientResource = new ExternalResource() { + + @Override + protected void after() { + if (httpclient != null) { + httpclient.shutdown(ShutdownType.GRACEFUL); + httpclient = null; + } + } + + }; + + public abstract HttpHost start() throws Exception; + + public final HttpHost start( + final HttpProcessor httpProcessor, + final Decorator exchangeHandlerDecorator, + final H1Config h1Config) throws Exception { + server.start(httpProcessor, exchangeHandlerDecorator, h1Config); + final Future endpointFuture = server.listen(new InetSocketAddress(0)); + httpclient = createClient(); + httpclient.start(); + final ListenerEndpoint endpoint = endpointFuture.get(); + final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress(); + return new HttpHost("localhost", address.getPort(), scheme.name()); + } + + public final HttpHost start( + final HttpProcessor httpProcessor, + final H1Config h1Config) throws Exception { + return start(httpProcessor, null, h1Config); + } + + public final HttpHost start( + final HttpProcessor httpProcessor, + final Decorator exchangeHandlerDecorator, + final H2Config h2Config) throws Exception { + server.start(httpProcessor, exchangeHandlerDecorator, h2Config); + final Future endpointFuture = server.listen(new InetSocketAddress(0)); + httpclient = createClient(); + httpclient.start(); + final ListenerEndpoint endpoint = endpointFuture.get(); + final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress(); + return new HttpHost("localhost", address.getPort(), scheme.name()); + } + + + public final HttpHost start( + final HttpProcessor httpProcessor, + final H2Config h2Config) throws Exception { + return start(httpProcessor, null, h2Config); + } + +} diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/LocalAsyncServerTestBase.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractServerTestBase.java similarity index 75% rename from httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/LocalAsyncServerTestBase.java rename to httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractServerTestBase.java index 5ae533c16..090dd688a 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/LocalAsyncServerTestBase.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractServerTestBase.java @@ -27,9 +27,6 @@ package org.apache.hc.client5.testing.async; -import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager; -import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; -import org.apache.hc.client5.http.ssl.H2TlsStrategy; import org.apache.hc.client5.testing.SSLTestContexts; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.URIScheme; @@ -41,23 +38,22 @@ import org.apache.hc.core5.util.Timeout; import org.junit.Rule; import org.junit.rules.ExternalResource; -public abstract class LocalAsyncServerTestBase { +public abstract class AbstractServerTestBase { public static final Timeout TIMEOUT = Timeout.ofSeconds(30); public static final Timeout LONG_TIMEOUT = Timeout.ofSeconds(60); protected final URIScheme scheme; - public LocalAsyncServerTestBase(final URIScheme scheme) { + public AbstractServerTestBase(final URIScheme scheme) { this.scheme = scheme; } - public LocalAsyncServerTestBase() { + public AbstractServerTestBase() { this(URIScheme.HTTP); } protected Http2TestServer server; - protected PoolingAsyncClientConnectionManager connManager; @Rule public ExternalResource serverResource = new ExternalResource() { @@ -97,24 +93,4 @@ public abstract class LocalAsyncServerTestBase { }; - @Rule - public ExternalResource connManagerResource = new ExternalResource() { - - @Override - protected void before() throws Throwable { - connManager = PoolingAsyncClientConnectionManagerBuilder.create() - .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext())) - .build(); - } - - @Override - protected void after() { - if (connManager != null) { - connManager.close(); - connManager = null; - } - } - - }; - } diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1Async.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1Async.java new file mode 100644 index 000000000..506001fb4 --- /dev/null +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1Async.java @@ -0,0 +1,198 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.hc.client5.testing.async; + +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; +import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder; +import org.apache.hc.client5.http.impl.async.HttpAsyncClients; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; +import org.apache.hc.client5.http.ssl.H2TlsStrategy; +import org.apache.hc.client5.testing.SSLTestContexts; +import org.apache.hc.core5.http.HeaderElements; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.URIScheme; +import org.apache.hc.core5.http.config.H1Config; +import org.hamcrest.CoreMatchers; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExternalResource; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class TestHttp1Async extends AbstractHttpAsyncFundamentalsTest { + + @Parameterized.Parameters(name = "HTTP/1.1 {0}") + public static Collection protocols() { + return Arrays.asList(new Object[][]{ + { URIScheme.HTTP }, + { URIScheme.HTTPS }, + }); + } + + protected HttpAsyncClientBuilder clientBuilder; + protected PoolingAsyncClientConnectionManager connManager; + + @Rule + public ExternalResource connManagerResource = new ExternalResource() { + + @Override + protected void before() throws Throwable { + connManager = PoolingAsyncClientConnectionManagerBuilder.create() + .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext())) + .build(); + } + + @Override + protected void after() { + if (connManager != null) { + connManager.close(); + connManager = null; + } + } + + }; + + @Rule + public ExternalResource clientResource = new ExternalResource() { + + @Override + protected void before() throws Throwable { + clientBuilder = HttpAsyncClientBuilder.create() + .setDefaultRequestConfig(RequestConfig.custom() + .setSocketTimeout(TIMEOUT) + .setConnectTimeout(TIMEOUT) + .setConnectionRequestTimeout(TIMEOUT) + .build()) + .setConnectionManager(connManager); + } + + }; + + public TestHttp1Async(final URIScheme scheme) { + super(scheme); + } + + @Override + protected CloseableHttpAsyncClient createClient() { + return clientBuilder.build(); + } + + @Override + public HttpHost start() throws Exception { + return super.start(null, H1Config.DEFAULT); + } + + @Test + public void testSequenctialGetRequestsCloseConnection() throws Exception { + final HttpHost target = start(); + for (int i = 0; i < 3; i++) { + final SimpleHttpRequest get = SimpleHttpRequest.get(target, "/random/2048"); + get.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE); + final Future future = httpclient.execute(get, null); + final SimpleHttpResponse response = future.get(); + Assert.assertThat(response, CoreMatchers.notNullValue()); + Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200)); + final String body = response.getBodyText(); + Assert.assertThat(body, CoreMatchers.notNullValue()); + Assert.assertThat(body.length(), CoreMatchers.equalTo(2048)); + } + } + + @Test + public void testConcurrentPostsOverMultipleConnections() throws Exception { + connManager.setDefaultMaxPerRoute(20); + connManager.setMaxTotal(100); + super.testConcurrentPostRequests(); + } + + @Test + public void testConcurrentPostsOverSingleConnection() throws Exception { + connManager.setDefaultMaxPerRoute(1); + connManager.setMaxTotal(100); + super.testConcurrentPostRequests(); + } + + @Test + public void testSharedPool() throws Exception { + final HttpHost target = start(); + final Future future1 = httpclient.execute( + SimpleHttpRequest.get(target, "/random/2048"), null); + final SimpleHttpResponse response1 = future1.get(); + Assert.assertThat(response1, CoreMatchers.notNullValue()); + Assert.assertThat(response1.getCode(), CoreMatchers.equalTo(200)); + final String body1 = response1.getBodyText(); + Assert.assertThat(body1, CoreMatchers.notNullValue()); + Assert.assertThat(body1.length(), CoreMatchers.equalTo(2048)); + + + try (final CloseableHttpAsyncClient httpclient2 = HttpAsyncClients.custom() + .setConnectionManager(connManager) + .setConnectionManagerShared(true) + .build()) { + httpclient2.start(); + final Future future2 = httpclient2.execute( + SimpleHttpRequest.get(target, "/random/2048"), null); + final SimpleHttpResponse response2 = future2.get(); + Assert.assertThat(response2, CoreMatchers.notNullValue()); + Assert.assertThat(response2.getCode(), CoreMatchers.equalTo(200)); + final String body2 = response2.getBodyText(); + Assert.assertThat(body2, CoreMatchers.notNullValue()); + Assert.assertThat(body2.length(), CoreMatchers.equalTo(2048)); + } + + final Future future3 = httpclient.execute( + SimpleHttpRequest.get(target, "/random/2048"), null); + final SimpleHttpResponse response3 = future3.get(); + Assert.assertThat(response3, CoreMatchers.notNullValue()); + Assert.assertThat(response3.getCode(), CoreMatchers.equalTo(200)); + final String body3 = response3.getBodyText(); + Assert.assertThat(body3, CoreMatchers.notNullValue()); + Assert.assertThat(body3.length(), CoreMatchers.equalTo(2048)); + } + + @Test + public void testBadRequest() throws Exception { + final HttpHost target = start(); + final Future future = httpclient.execute( + SimpleHttpRequest.get(target, "/random/boom"), null); + final SimpleHttpResponse response = future.get(); + Assert.assertThat(response, CoreMatchers.notNullValue()); + Assert.assertThat(response.getCode(), CoreMatchers.equalTo(400)); + } + +} \ No newline at end of file diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncRedirects.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncRedirects.java new file mode 100644 index 000000000..48caa356d --- /dev/null +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncRedirects.java @@ -0,0 +1,254 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.hc.client5.testing.async; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; +import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.client5.http.ssl.H2TlsStrategy; +import org.apache.hc.client5.testing.SSLTestContexts; +import org.apache.hc.core5.function.Supplier; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.ProtocolException; +import org.apache.hc.core5.http.URIScheme; +import org.apache.hc.core5.http.config.H1Config; +import org.apache.hc.core5.http.message.BasicHeader; +import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; +import org.apache.hc.core5.http.protocol.HttpCoreContext; +import org.apache.hc.core5.net.URIBuilder; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExternalResource; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +/** + * Redirection test cases. + */ +@RunWith(Parameterized.class) +public class TestHttp1AsyncRedirects extends AbstractHttpAsyncRedirectsTest { + + @Parameterized.Parameters(name = "HTTP/1.1 {0}") + public static Collection protocols() { + return Arrays.asList(new Object[][]{ + {URIScheme.HTTP}, + {URIScheme.HTTPS}, + }); + } + + protected HttpAsyncClientBuilder clientBuilder; + protected PoolingAsyncClientConnectionManager connManager; + + @Rule + public ExternalResource connManagerResource = new ExternalResource() { + + @Override + protected void before() throws Throwable { + connManager = PoolingAsyncClientConnectionManagerBuilder.create() + .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext())) + .build(); + } + + @Override + protected void after() { + if (connManager != null) { + connManager.close(); + connManager = null; + } + } + + }; + + @Rule + public ExternalResource clientResource = new ExternalResource() { + + @Override + protected void before() throws Throwable { + clientBuilder = HttpAsyncClientBuilder.create() + .setDefaultRequestConfig(RequestConfig.custom() + .setSocketTimeout(TIMEOUT) + .setConnectTimeout(TIMEOUT) + .setConnectionRequestTimeout(TIMEOUT) + .build()) + .setConnectionManager(connManager); + } + + }; + + public TestHttp1AsyncRedirects(final URIScheme scheme) { + super(scheme); + } + + @Override + protected CloseableHttpAsyncClient createClient() throws Exception { + return clientBuilder.build(); + } + + @Override + public final HttpHost start() throws Exception { + return super.start(null, H1Config.DEFAULT); + } + + static class NoKeepAliveRedirectService extends AbstractSimpleServerExchangeHandler { + + private final int statuscode; + + public NoKeepAliveRedirectService(final int statuscode) { + super(); + this.statuscode = statuscode; + } + + @Override + protected SimpleHttpResponse handle( + final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException { + try { + final URI requestURI = request.getUri(); + final String path = requestURI.getPath(); + if (path.equals("/oldlocation/")) { + final SimpleHttpResponse response = new SimpleHttpResponse(statuscode); + response.addHeader(new BasicHeader("Location", + new URIBuilder(requestURI).setPath("/newlocation/").build())); + response.addHeader(new BasicHeader("Connection", "close")); + return response; + } else if (path.equals("/newlocation/")) { + final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_OK); + response.setBodyText("Successful redirect", ContentType.TEXT_PLAIN); + return response; + } else { + return new SimpleHttpResponse(HttpStatus.SC_NOT_FOUND); + } + } catch (final URISyntaxException ex) { + throw new ProtocolException(ex.getMessage(), ex); + } + } + + } + + @Test + public void testBasicRedirect300() throws Exception { + server.register("*", new Supplier() { + + @Override + public AsyncServerExchangeHandler get() { + return new NoKeepAliveRedirectService(HttpStatus.SC_MULTIPLE_CHOICES); + } + + }); + final HttpHost target = start(); + + final HttpClientContext context = HttpClientContext.create(); + final Future future = httpclient.execute( + SimpleHttpRequest.get(target, "/oldlocation/"), context, null); + final HttpResponse response = future.get(); + Assert.assertNotNull(response); + + final HttpRequest request = context.getRequest(); + + Assert.assertEquals(HttpStatus.SC_MULTIPLE_CHOICES, response.getCode()); + Assert.assertEquals("/oldlocation/", request.getRequestUri()); + } + + @Test + public void testBasicRedirect301NoKeepAlive() throws Exception { + server.register("*", new Supplier() { + + @Override + public AsyncServerExchangeHandler get() { + return new NoKeepAliveRedirectService(HttpStatus.SC_MOVED_PERMANENTLY); + } + + }); + + final HttpHost target = start(); + final HttpClientContext context = HttpClientContext.create(); + final Future future = httpclient.execute( + SimpleHttpRequest.get(target, "/oldlocation/"), context, null); + final HttpResponse response = future.get(); + Assert.assertNotNull(response); + + final HttpRequest request = context.getRequest(); + + Assert.assertEquals(HttpStatus.SC_OK, response.getCode()); + Assert.assertEquals("/newlocation/", request.getRequestUri()); + Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme())); + } + + @Test + public void testDefaultHeadersRedirect() throws Exception { + server.register("*", new Supplier() { + + @Override + public AsyncServerExchangeHandler get() { + return new NoKeepAliveRedirectService(HttpStatus.SC_MOVED_TEMPORARILY); + } + + }); + + final List
defaultHeaders = new ArrayList<>(1); + defaultHeaders.add(new BasicHeader(HttpHeaders.USER_AGENT, "my-test-client")); + clientBuilder.setDefaultHeaders(defaultHeaders); + + final HttpHost target = start(); + + final HttpClientContext context = HttpClientContext.create(); + + final Future future = httpclient.execute( + SimpleHttpRequest.get(target, "/oldlocation/"), context, null); + final HttpResponse response = future.get(); + Assert.assertNotNull(response); + + final HttpRequest request = context.getRequest(); + + Assert.assertEquals(HttpStatus.SC_OK, response.getCode()); + Assert.assertEquals("/newlocation/", request.getRequestUri()); + + final Header header = request.getFirstHeader(HttpHeaders.USER_AGENT); + Assert.assertEquals("my-test-client", header.getValue()); + } + +} \ No newline at end of file diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncStatefulConnManagement.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncStatefulConnManagement.java similarity index 82% rename from httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncStatefulConnManagement.java rename to httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncStatefulConnManagement.java index bbede2559..7880f6c1b 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncStatefulConnManagement.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncStatefulConnManagement.java @@ -32,8 +32,14 @@ import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.UserTokenHandler; import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.client5.http.ssl.H2TlsStrategy; +import org.apache.hc.client5.testing.SSLTestContexts; import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.EndpointDetails; @@ -41,14 +47,66 @@ import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.config.H1Config; import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.protocol.BasicHttpContext; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.junit.Assert; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExternalResource; -public class TestAsyncStatefulConnManagement extends IntegrationTestBase { +public class TestHttp1AsyncStatefulConnManagement extends AbstractIntegrationTestBase { + + protected HttpAsyncClientBuilder clientBuilder; + protected PoolingAsyncClientConnectionManager connManager; + + @Rule + public ExternalResource connManagerResource = new ExternalResource() { + + @Override + protected void before() throws Throwable { + connManager = PoolingAsyncClientConnectionManagerBuilder.create() + .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext())) + .build(); + } + + @Override + protected void after() { + if (connManager != null) { + connManager.close(); + connManager = null; + } + } + + }; + + @Rule + public ExternalResource clientResource = new ExternalResource() { + + @Override + protected void before() throws Throwable { + clientBuilder = HttpAsyncClientBuilder.create() + .setDefaultRequestConfig(RequestConfig.custom() + .setSocketTimeout(TIMEOUT) + .setConnectTimeout(TIMEOUT) + .setConnectionRequestTimeout(TIMEOUT) + .build()) + .setConnectionManager(connManager); + } + + }; + + @Override + protected CloseableHttpAsyncClient createClient() throws Exception { + return clientBuilder.build(); + } + + @Override + public HttpHost start() throws Exception { + return super.start(null, H1Config.DEFAULT); + } @Test public void testStatefulConnections() throws Exception { diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1ClientAuthentication.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1ClientAuthentication.java new file mode 100644 index 000000000..e998c4326 --- /dev/null +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1ClientAuthentication.java @@ -0,0 +1,180 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.hc.client5.testing.async; + +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.AuthenticationStrategy; +import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; +import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.client5.http.auth.AuthSchemeProvider; +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.client5.http.ssl.H2TlsStrategy; +import org.apache.hc.client5.testing.BasicTestAuthenticator; +import org.apache.hc.client5.testing.SSLTestContexts; +import org.apache.hc.core5.function.Decorator; +import org.apache.hc.core5.function.Supplier; +import org.apache.hc.core5.http.HeaderElements; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.HttpVersion; +import org.apache.hc.core5.http.URIScheme; +import org.apache.hc.core5.http.config.H1Config; +import org.apache.hc.core5.http.config.Lookup; +import org.apache.hc.core5.http.impl.HttpProcessors; +import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExternalResource; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class TestHttp1ClientAuthentication extends AbstractHttpAsyncClientAuthentication { + + @Parameterized.Parameters(name = "HTTP/1.1 {0}") + public static Collection protocols() { + return Arrays.asList(new Object[][]{ + {URIScheme.HTTP}, + {URIScheme.HTTPS}, + }); + } + + protected HttpAsyncClientBuilder clientBuilder; + protected PoolingAsyncClientConnectionManager connManager; + + @Rule + public ExternalResource connManagerResource = new ExternalResource() { + + @Override + protected void before() throws Throwable { + connManager = PoolingAsyncClientConnectionManagerBuilder.create() + .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext())) + .build(); + } + + @Override + protected void after() { + if (connManager != null) { + connManager.close(); + connManager = null; + } + } + + }; + + @Rule + public ExternalResource clientResource = new ExternalResource() { + + @Override + protected void before() throws Throwable { + clientBuilder = HttpAsyncClientBuilder.create() + .setDefaultRequestConfig(RequestConfig.custom() + .setSocketTimeout(TIMEOUT) + .setConnectTimeout(TIMEOUT) + .setConnectionRequestTimeout(TIMEOUT) + .build()) + .setConnectionManager(connManager); + } + + }; + + public TestHttp1ClientAuthentication(final URIScheme scheme) { + super(scheme, HttpVersion.HTTP_1_1); + } + + @Override + void setDefaultAuthSchemeRegistry(final Lookup authSchemeRegistry) { + clientBuilder.setDefaultAuthSchemeRegistry(authSchemeRegistry); + } + + @Override + void setTargetAuthenticationStrategy(final AuthenticationStrategy targetAuthStrategy) { + clientBuilder.setTargetAuthenticationStrategy(targetAuthStrategy); + } + + @Override + protected CloseableHttpAsyncClient createClient() throws Exception { + return clientBuilder.build(); + } + + @Test + public void testBasicAuthenticationSuccessNonPersistentConnection() throws Exception { + server.register("*", new Supplier() { + + @Override + public AsyncServerExchangeHandler get() { + return new AsyncEchoHandler(); + } + + }); + final HttpHost target = start( + HttpProcessors.server(), + new Decorator() { + + @Override + public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) { + return new AuthenticatingAsyncDecorator(exchangeHandler, new BasicTestAuthenticator("test:test", "test realm")) { + + @Override + protected void customizeUnauthorizedResponse(final HttpResponse unauthorized) { + unauthorized.addHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE); + } + }; + } + + }, + H1Config.DEFAULT); + + final TestCredentialsProvider credsProvider = new TestCredentialsProvider( + new UsernamePasswordCredentials("test", "test".toCharArray())); + final HttpClientContext context = HttpClientContext.create(); + context.setCredentialsProvider(credsProvider); + + final Future future = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null); + final HttpResponse response = future.get(); + + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.SC_OK, response.getCode()); + final AuthScope authscope = credsProvider.getAuthScope(); + Assert.assertNotNull(authscope); + Assert.assertEquals("test realm", authscope.getRealm()); + } + +} \ No newline at end of file diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2Async.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2Async.java new file mode 100644 index 000000000..b0035e932 --- /dev/null +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2Async.java @@ -0,0 +1,88 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.hc.client5.testing.async; + +import java.util.Arrays; +import java.util.Collection; + +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.Http2AsyncClientBuilder; +import org.apache.hc.client5.http.ssl.H2TlsStrategy; +import org.apache.hc.client5.testing.SSLTestContexts; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.URIScheme; +import org.apache.hc.core5.http2.config.H2Config; +import org.junit.Rule; +import org.junit.rules.ExternalResource; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class TestHttp2Async extends AbstractHttpAsyncFundamentalsTest { + + @Parameterized.Parameters(name = "HTTP/2 {0}") + public static Collection protocols() { + return Arrays.asList(new Object[][]{ + { URIScheme.HTTP }, + { URIScheme.HTTPS } + }); + } + + protected Http2AsyncClientBuilder clientBuilder; + + @Rule + public ExternalResource clientResource = new ExternalResource() { + + @Override + protected void before() throws Throwable { + clientBuilder = Http2AsyncClientBuilder.create() + .setDefaultRequestConfig(RequestConfig.custom() + .setSocketTimeout(TIMEOUT) + .setConnectTimeout(TIMEOUT) + .setConnectionRequestTimeout(TIMEOUT) + .build()) + .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext())); + } + + }; + + public TestHttp2Async(final URIScheme scheme) { + super(scheme); + } + + @Override + protected CloseableHttpAsyncClient createClient() { + return clientBuilder.build(); + } + + @Override + public HttpHost start() throws Exception { + return super.start(null, H2Config.DEFAULT); + } + +} \ No newline at end of file diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2AsyncMinimal.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2AsyncMinimal.java index 58765a275..a3369dea3 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2AsyncMinimal.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2AsyncMinimal.java @@ -26,50 +26,22 @@ */ package org.apache.hc.client5.testing.async; -import java.net.InetSocketAddress; import java.util.Arrays; import java.util.Collection; -import java.util.LinkedList; -import java.util.Queue; -import java.util.Random; -import java.util.concurrent.Future; -import org.apache.hc.client5.http.async.methods.AsyncRequestBuilder; -import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; -import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; import org.apache.hc.client5.http.impl.async.HttpAsyncClients; import org.apache.hc.client5.http.impl.async.MinimalHttp2AsyncClient; -import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.client5.http.ssl.H2TlsStrategy; import org.apache.hc.client5.testing.SSLTestContexts; -import org.apache.hc.core5.function.Supplier; -import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpHost; -import org.apache.hc.core5.http.HttpResponse; -import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.URIScheme; -import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; -import org.apache.hc.core5.http.nio.BasicResponseConsumer; -import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityConsumer; import org.apache.hc.core5.http2.config.H2Config; -import org.apache.hc.core5.io.ShutdownType; import org.apache.hc.core5.reactor.IOReactorConfig; -import org.apache.hc.core5.reactor.ListenerEndpoint; -import org.apache.hc.core5.testing.nio.Http2TestServer; -import org.apache.hc.core5.util.TimeValue; -import org.apache.hc.core5.util.Timeout; -import org.hamcrest.CoreMatchers; -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExternalResource; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @RunWith(Parameterized.class) -public class TestHttp2AsyncMinimal { - - public static final Timeout TIMEOUT = Timeout.ofSeconds(30); +public class TestHttp2AsyncMinimal extends AbstractHttpAsyncFundamentalsTest { @Parameterized.Parameters(name = "{0}") public static Collection protocols() { @@ -79,141 +51,22 @@ public class TestHttp2AsyncMinimal { }); } - protected final URIScheme scheme; - public TestHttp2AsyncMinimal(final URIScheme scheme) { - this.scheme = scheme; + super(scheme); } - protected Http2TestServer server; - protected MinimalHttp2AsyncClient httpclient; - - @Rule - public ExternalResource serverResource = new ExternalResource() { - - @Override - protected void before() throws Throwable { - server = new Http2TestServer( - IOReactorConfig.custom() - .setSoTimeout(TIMEOUT) - .build(), - scheme == URIScheme.HTTPS ? SSLTestContexts.createServerSSLContext() : null); - server.register("/echo/*", new Supplier() { - - @Override - public AsyncServerExchangeHandler get() { - return new AsyncEchoHandler(); - } - - }); - server.register("/random/*", new Supplier() { - - @Override - public AsyncServerExchangeHandler get() { - return new AsyncRandomHandler(); - } - - }); - } - - @Override - protected void after() { - if (server != null) { - server.shutdown(TimeValue.ofSeconds(5)); - server = null; - } - } - - }; - - @Rule - public ExternalResource clientResource = new ExternalResource() { - - @Override - protected void before() throws Throwable { - final IOReactorConfig ioReactorConfig = IOReactorConfig.custom() - .setSoTimeout(TIMEOUT) - .build(); - httpclient = HttpAsyncClients.createHttp2Minimal( - H2Config.DEFAULT, ioReactorConfig, new H2TlsStrategy(SSLTestContexts.createClientSSLContext())); - } - - @Override - protected void after() { - if (httpclient != null) { - httpclient.shutdown(ShutdownType.GRACEFUL); - httpclient = null; - } - } - - }; + @Override + protected MinimalHttp2AsyncClient createClient() throws Exception { + final IOReactorConfig ioReactorConfig = IOReactorConfig.custom() + .setSoTimeout(TIMEOUT) + .build(); + return HttpAsyncClients.createHttp2Minimal( + H2Config.DEFAULT, ioReactorConfig, new H2TlsStrategy(SSLTestContexts.createClientSSLContext())); + } + @Override public HttpHost start() throws Exception { - server.start(H2Config.DEFAULT); - final Future endpointFuture = server.listen(new InetSocketAddress(0)); - httpclient.start(); - final ListenerEndpoint endpoint = endpointFuture.get(); - final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress(); - return new HttpHost("localhost", address.getPort(), scheme.name()); - } - - @Test - public void testSequenctialGetRequests() throws Exception { - final HttpHost target = start(); - for (int i = 0; i < 3; i++) { - final Future future = httpclient.execute( - SimpleHttpRequest.get(target, "/random/2048"), null); - final SimpleHttpResponse response = future.get(); - Assert.assertThat(response, CoreMatchers.notNullValue()); - Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200)); - final String body = response.getBodyText(); - Assert.assertThat(body, CoreMatchers.notNullValue()); - Assert.assertThat(body.length(), CoreMatchers.equalTo(2048)); - } - } - - @Test - public void testSequenctialHeadRequests() throws Exception { - final HttpHost target = start(); - for (int i = 0; i < 3; i++) { - final Future future = httpclient.execute( - SimpleHttpRequest.head(target, "/random/2048"), null); - final SimpleHttpResponse response = future.get(); - Assert.assertThat(response, CoreMatchers.notNullValue()); - Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200)); - final String body = response.getBodyText(); - Assert.assertThat(body, CoreMatchers.nullValue()); - } - } - - @Test - public void testConcurrentPostsOver() throws Exception { - final HttpHost target = start(); - final byte[] b1 = new byte[1024]; - final Random rnd = new Random(System.currentTimeMillis()); - rnd.nextBytes(b1); - - final int reqCount = 20; - - final Queue>> queue = new LinkedList<>(); - for (int i = 0; i < reqCount; i++) { - final Future> future = httpclient.execute( - AsyncRequestBuilder.post(target, "/echo/") - .setEntity(b1, ContentType.APPLICATION_OCTET_STREAM) - .build(), - new BasicResponseConsumer<>(new BasicAsyncEntityConsumer()), HttpClientContext.create(), null); - queue.add(future); - } - - while (!queue.isEmpty()) { - final Future> future = queue.remove(); - final Message responseMessage = future.get(); - Assert.assertThat(responseMessage, CoreMatchers.notNullValue()); - final HttpResponse response = responseMessage.getHead(); - Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200)); - final byte[] b2 = responseMessage.getBody(); - Assert.assertThat(b1, CoreMatchers.equalTo(b2)); - } + return super.start(null, H2Config.DEFAULT); } } \ No newline at end of file diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2AsyncRedirect.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2AsyncRedirect.java new file mode 100644 index 000000000..63011afe4 --- /dev/null +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2AsyncRedirect.java @@ -0,0 +1,88 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.hc.client5.testing.async; + +import java.util.Arrays; +import java.util.Collection; + +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.Http2AsyncClientBuilder; +import org.apache.hc.client5.http.ssl.H2TlsStrategy; +import org.apache.hc.client5.testing.SSLTestContexts; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.URIScheme; +import org.apache.hc.core5.http2.config.H2Config; +import org.junit.Rule; +import org.junit.rules.ExternalResource; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class TestHttp2AsyncRedirect extends AbstractHttpAsyncRedirectsTest { + + @Parameterized.Parameters(name = "HTTP/2 {0}") + public static Collection protocols() { + return Arrays.asList(new Object[][]{ + { URIScheme.HTTP }, + { URIScheme.HTTPS } + }); + } + + protected Http2AsyncClientBuilder clientBuilder; + + @Rule + public ExternalResource clientResource = new ExternalResource() { + + @Override + protected void before() throws Throwable { + clientBuilder = Http2AsyncClientBuilder.create() + .setDefaultRequestConfig(RequestConfig.custom() + .setSocketTimeout(TIMEOUT) + .setConnectTimeout(TIMEOUT) + .setConnectionRequestTimeout(TIMEOUT) + .build()) + .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext())); + } + + }; + + public TestHttp2AsyncRedirect(final URIScheme scheme) { + super(scheme); + } + + @Override + protected CloseableHttpAsyncClient createClient() { + return clientBuilder.build(); + } + + @Override + public HttpHost start() throws Exception { + return super.start(null, H2Config.DEFAULT); + } + +} \ No newline at end of file diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2ClientAuthentication.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2ClientAuthentication.java new file mode 100644 index 000000000..5cb1d8074 --- /dev/null +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2ClientAuthentication.java @@ -0,0 +1,97 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.hc.client5.testing.async; + +import java.util.Arrays; +import java.util.Collection; + +import org.apache.hc.client5.http.AuthenticationStrategy; +import org.apache.hc.client5.http.auth.AuthSchemeProvider; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.Http2AsyncClientBuilder; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager; +import org.apache.hc.client5.http.ssl.H2TlsStrategy; +import org.apache.hc.client5.testing.SSLTestContexts; +import org.apache.hc.core5.http.HttpVersion; +import org.apache.hc.core5.http.URIScheme; +import org.apache.hc.core5.http.config.Lookup; +import org.junit.Rule; +import org.junit.rules.ExternalResource; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class TestHttp2ClientAuthentication extends AbstractHttpAsyncClientAuthentication { + + @Parameterized.Parameters(name = "HTTP/2 {0}") + public static Collection protocols() { + return Arrays.asList(new Object[][]{ + {URIScheme.HTTP}, + {URIScheme.HTTPS}, + }); + } + + protected Http2AsyncClientBuilder clientBuilder; + protected PoolingAsyncClientConnectionManager connManager; + + @Rule + public ExternalResource clientResource = new ExternalResource() { + + @Override + protected void before() throws Throwable { + clientBuilder = Http2AsyncClientBuilder.create() + .setDefaultRequestConfig(RequestConfig.custom() + .setSocketTimeout(TIMEOUT) + .setConnectTimeout(TIMEOUT) + .setConnectionRequestTimeout(TIMEOUT) + .build()) + .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext())); + } + + }; + + public TestHttp2ClientAuthentication(final URIScheme scheme) { + super(scheme, HttpVersion.HTTP_2); + } + + @Override + void setDefaultAuthSchemeRegistry(final Lookup authSchemeRegistry) { + clientBuilder.setDefaultAuthSchemeRegistry(authSchemeRegistry); + } + + @Override + void setTargetAuthenticationStrategy(final AuthenticationStrategy targetAuthStrategy) { + clientBuilder.setTargetAuthenticationStrategy(targetAuthStrategy); + } + + @Override + protected CloseableHttpAsyncClient createClient() throws Exception { + return clientBuilder.build(); + } + +} \ No newline at end of file diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsyncMinimal.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsyncMinimal.java index b7b1ef50b..61737116d 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsyncMinimal.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsyncMinimal.java @@ -26,7 +26,6 @@ */ package org.apache.hc.client5.testing.async; -import java.net.InetSocketAddress; import java.util.Arrays; import java.util.Collection; import java.util.LinkedList; @@ -36,8 +35,6 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.apache.hc.client5.http.async.methods.AsyncRequestBuilder; -import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; -import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; import org.apache.hc.client5.http.impl.async.HttpAsyncClients; import org.apache.hc.client5.http.impl.async.MinimalHttpAsyncClient; import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager; @@ -45,7 +42,6 @@ import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBu import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.client5.http.ssl.H2TlsStrategy; import org.apache.hc.client5.testing.SSLTestContexts; -import org.apache.hc.core5.function.Supplier; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpResponse; @@ -54,31 +50,21 @@ import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.config.H1Config; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; -import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; import org.apache.hc.core5.http.nio.BasicResponseConsumer; import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityConsumer; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; -import org.apache.hc.core5.io.ShutdownType; import org.apache.hc.core5.reactor.IOReactorConfig; -import org.apache.hc.core5.reactor.ListenerEndpoint; -import org.apache.hc.core5.testing.nio.Http2TestServer; -import org.apache.hc.core5.util.TimeValue; -import org.apache.hc.core5.util.Timeout; import org.hamcrest.CoreMatchers; import org.junit.Assert; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExternalResource; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @RunWith(Parameterized.class) -public class TestHttpAsyncMinimal { +public class TestHttpAsyncMinimal extends AbstractHttpAsyncFundamentalsTest { - public static final Timeout TIMEOUT = Timeout.ofSeconds(30); - - @Parameterized.Parameters(name = "{0} {1}") + @Parameterized.Parameters(name = "Minimal {0} {1}") public static Collection protocols() { return Arrays.asList(new Object[][]{ { HttpVersion.HTTP_1_1, URIScheme.HTTP }, @@ -89,158 +75,40 @@ public class TestHttpAsyncMinimal { } protected final HttpVersion version; - protected final URIScheme scheme; public TestHttpAsyncMinimal(final HttpVersion version, final URIScheme scheme) { + super(scheme); this.version = version; - this.scheme = scheme; } - protected Http2TestServer server; - protected MinimalHttpAsyncClient httpclient; - - @Rule - public ExternalResource serverResource = new ExternalResource() { - - @Override - protected void before() throws Throwable { - server = new Http2TestServer( - IOReactorConfig.custom() - .setSoTimeout(TIMEOUT) - .build(), - scheme == URIScheme.HTTPS ? SSLTestContexts.createServerSSLContext() : null); - server.register("/echo/*", new Supplier() { - - @Override - public AsyncServerExchangeHandler get() { - return new AsyncEchoHandler(); - } - - }); - server.register("/random/*", new Supplier() { - - @Override - public AsyncServerExchangeHandler get() { - return new AsyncRandomHandler(); - } - - }); + @Override + protected MinimalHttpAsyncClient createClient() throws Exception { + final PoolingAsyncClientConnectionManager connectionManager = PoolingAsyncClientConnectionManagerBuilder.create() + .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext())) + .build(); + final IOReactorConfig ioReactorConfig = IOReactorConfig.custom() + .setSoTimeout(TIMEOUT) + .build(); + if (version.greaterEquals(HttpVersion.HTTP_2)) { + return HttpAsyncClients.createMinimal( + HttpVersionPolicy.FORCE_HTTP_2, H2Config.DEFAULT, H1Config.DEFAULT, ioReactorConfig, connectionManager); + } else { + return HttpAsyncClients.createMinimal( + HttpVersionPolicy.FORCE_HTTP_1, H2Config.DEFAULT, H1Config.DEFAULT, ioReactorConfig, connectionManager); } + } - @Override - protected void after() { - if (server != null) { - server.shutdown(TimeValue.ofSeconds(5)); - server = null; - } - } - - }; - - @Rule - public ExternalResource clientResource = new ExternalResource() { - - @Override - protected void before() throws Throwable { - final PoolingAsyncClientConnectionManager connectionManager = PoolingAsyncClientConnectionManagerBuilder.create() - .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext())) - .build(); - final IOReactorConfig ioReactorConfig = IOReactorConfig.custom() - .setSoTimeout(TIMEOUT) - .build(); - if (version.greaterEquals(HttpVersion.HTTP_2)) { - httpclient = HttpAsyncClients.createMinimal( - HttpVersionPolicy.FORCE_HTTP_2, H2Config.DEFAULT, H1Config.DEFAULT, ioReactorConfig, connectionManager); - } else { - httpclient = HttpAsyncClients.createMinimal( - HttpVersionPolicy.FORCE_HTTP_1, H2Config.DEFAULT, H1Config.DEFAULT, ioReactorConfig, connectionManager); - } - } - - @Override - protected void after() { - if (httpclient != null) { - httpclient.shutdown(ShutdownType.GRACEFUL); - httpclient = null; - } - } - - }; - + @Override public HttpHost start() throws Exception { if (version.greaterEquals(HttpVersion.HTTP_2)) { - server.start(H2Config.DEFAULT); + return super.start(null, H2Config.DEFAULT); } else { - server.start(H1Config.DEFAULT); - } - final Future endpointFuture = server.listen(new InetSocketAddress(0)); - httpclient.start(); - final ListenerEndpoint endpoint = endpointFuture.get(); - final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress(); - return new HttpHost("localhost", address.getPort(), scheme.name()); - } - - @Test - public void testSequenctialGetRequests() throws Exception { - final HttpHost target = start(); - for (int i = 0; i < 3; i++) { - final Future future = httpclient.execute( - SimpleHttpRequest.get(target, "/random/2048"), null); - final SimpleHttpResponse response = future.get(); - Assert.assertThat(response, CoreMatchers.notNullValue()); - Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200)); - final String body = response.getBodyText(); - Assert.assertThat(body, CoreMatchers.notNullValue()); - Assert.assertThat(body.length(), CoreMatchers.equalTo(2048)); + return super.start(null, H1Config.DEFAULT); } } @Test - public void testSequenctialHeadRequests() throws Exception { - final HttpHost target = start(); - for (int i = 0; i < 3; i++) { - final Future future = httpclient.execute( - SimpleHttpRequest.head(target, "/random/2048"), null); - final SimpleHttpResponse response = future.get(); - Assert.assertThat(response, CoreMatchers.notNullValue()); - Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200)); - final String body = response.getBodyText(); - Assert.assertThat(body, CoreMatchers.nullValue()); - } - } - - @Test - public void testConcurrentPostsOverMultipleConnections() throws Exception { - final HttpHost target = start(); - final byte[] b1 = new byte[1024]; - final Random rnd = new Random(System.currentTimeMillis()); - rnd.nextBytes(b1); - - final int reqCount = 20; - - final Queue>> queue = new LinkedList<>(); - for (int i = 0; i < reqCount; i++) { - final Future> future = httpclient.execute( - AsyncRequestBuilder.post(target, "/echo/") - .setEntity(b1, ContentType.APPLICATION_OCTET_STREAM) - .build(), - new BasicResponseConsumer<>(new BasicAsyncEntityConsumer()), HttpClientContext.create(), null); - queue.add(future); - } - - while (!queue.isEmpty()) { - final Future> future = queue.remove(); - final Message responseMessage = future.get(); - Assert.assertThat(responseMessage, CoreMatchers.notNullValue()); - final HttpResponse response = responseMessage.getHead(); - Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200)); - final byte[] b2 = responseMessage.getBody(); - Assert.assertThat(b1, CoreMatchers.equalTo(b2)); - } - } - - @Test - public void testConcurrentPostsOverSingleConnection() throws Exception { + public void testConcurrentPostRequestsSameEndpoint() throws Exception { final HttpHost target = start(); final byte[] b1 = new byte[1024]; final Random rnd = new Random(System.currentTimeMillis()); diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncClientBuilder.java new file mode 100644 index 000000000..1728709c8 --- /dev/null +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncClientBuilder.java @@ -0,0 +1,885 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.hc.client5.http.impl.async; + +import java.io.Closeable; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ThreadFactory; + +import org.apache.hc.client5.http.AuthenticationStrategy; +import org.apache.hc.client5.http.DnsResolver; +import org.apache.hc.client5.http.HttpRequestRetryHandler; +import org.apache.hc.client5.http.SchemePortResolver; +import org.apache.hc.client5.http.SystemDefaultDnsResolver; +import org.apache.hc.client5.http.async.AsyncExecChainHandler; +import org.apache.hc.client5.http.auth.AuthSchemeProvider; +import org.apache.hc.client5.http.auth.CredentialsProvider; +import org.apache.hc.client5.http.auth.KerberosConfig; +import org.apache.hc.client5.http.config.AuthSchemes; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.cookie.BasicCookieStore; +import org.apache.hc.client5.http.cookie.CookieSpecProvider; +import org.apache.hc.client5.http.cookie.CookieStore; +import org.apache.hc.client5.http.impl.ChainElements; +import org.apache.hc.client5.http.impl.CookieSpecRegistries; +import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy; +import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryHandler; +import org.apache.hc.client5.http.impl.DefaultRedirectStrategy; +import org.apache.hc.client5.http.impl.DefaultSchemePortResolver; +import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +import org.apache.hc.client5.http.impl.auth.BasicSchemeFactory; +import org.apache.hc.client5.http.impl.auth.CredSspSchemeFactory; +import org.apache.hc.client5.http.impl.auth.DigestSchemeFactory; +import org.apache.hc.client5.http.impl.auth.KerberosSchemeFactory; +import org.apache.hc.client5.http.impl.auth.NTLMSchemeFactory; +import org.apache.hc.client5.http.impl.auth.SPNegoSchemeFactory; +import org.apache.hc.client5.http.impl.auth.SystemDefaultCredentialsProvider; +import org.apache.hc.client5.http.impl.nio.MultuhomeConnectionInitiator; +import org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner; +import org.apache.hc.client5.http.protocol.RedirectStrategy; +import org.apache.hc.client5.http.protocol.RequestAddCookies; +import org.apache.hc.client5.http.protocol.RequestAuthCache; +import org.apache.hc.client5.http.protocol.RequestDefaultHeaders; +import org.apache.hc.client5.http.protocol.RequestExpectContinue; +import org.apache.hc.client5.http.protocol.ResponseProcessCookies; +import org.apache.hc.client5.http.routing.HttpRoutePlanner; +import org.apache.hc.client5.http.ssl.H2TlsStrategy; +import org.apache.hc.core5.annotation.Internal; +import org.apache.hc.core5.concurrent.DefaultThreadFactory; +import org.apache.hc.core5.function.Callback; +import org.apache.hc.core5.function.Resolver; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpRequestInterceptor; +import org.apache.hc.core5.http.HttpResponseInterceptor; +import org.apache.hc.core5.http.config.CharCodingConfig; +import org.apache.hc.core5.http.config.Lookup; +import org.apache.hc.core5.http.config.NamedElementChain; +import org.apache.hc.core5.http.config.RegistryBuilder; +import org.apache.hc.core5.http.nio.AsyncPushConsumer; +import org.apache.hc.core5.http.nio.HandlerFactory; +import org.apache.hc.core5.http.nio.command.ShutdownCommand; +import org.apache.hc.core5.http.nio.ssl.TlsStrategy; +import org.apache.hc.core5.http.protocol.DefaultHttpProcessor; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.http.protocol.HttpProcessor; +import org.apache.hc.core5.http.protocol.HttpProcessorBuilder; +import org.apache.hc.core5.http.protocol.RequestTargetHost; +import org.apache.hc.core5.http.protocol.RequestUserAgent; +import org.apache.hc.core5.http2.config.H2Config; +import org.apache.hc.core5.http2.nio.pool.H2ConnPool; +import org.apache.hc.core5.http2.protocol.H2RequestConnControl; +import org.apache.hc.core5.http2.protocol.H2RequestContent; +import org.apache.hc.core5.http2.protocol.H2RequestTargetHost; +import org.apache.hc.core5.io.ShutdownType; +import org.apache.hc.core5.reactor.DefaultConnectingIOReactor; +import org.apache.hc.core5.reactor.IOEventHandlerFactory; +import org.apache.hc.core5.reactor.IOReactorConfig; +import org.apache.hc.core5.reactor.IOSession; +import org.apache.hc.core5.util.Args; +import org.apache.hc.core5.util.TimeValue; +import org.apache.hc.core5.util.VersionInfo; + +/** + * Builder for HTTP/2 {@link CloseableHttpAsyncClient} instances. + *

+ * When a particular component is not explicitly set this class will + * use its default implementation. + * + * @since 5.0 + */ +public class Http2AsyncClientBuilder { + + private static class RequestInterceptorEntry { + + enum Postion { FIRST, LAST } + + final RequestInterceptorEntry.Postion postion; + final HttpRequestInterceptor interceptor; + + private RequestInterceptorEntry(final RequestInterceptorEntry.Postion postion, final HttpRequestInterceptor interceptor) { + this.postion = postion; + this.interceptor = interceptor; + } + } + + private static class ResponseInterceptorEntry { + + enum Postion { FIRST, LAST } + + final ResponseInterceptorEntry.Postion postion; + final HttpResponseInterceptor interceptor; + + private ResponseInterceptorEntry(final ResponseInterceptorEntry.Postion postion, final HttpResponseInterceptor interceptor) { + this.postion = postion; + this.interceptor = interceptor; + } + } + + private static class ExecInterceptorEntry { + + enum Postion { BEFORE, AFTER, REPLACE, FIRST, LAST } + + final ExecInterceptorEntry.Postion postion; + final String name; + final AsyncExecChainHandler interceptor; + final String existing; + + private ExecInterceptorEntry( + final ExecInterceptorEntry.Postion postion, + final String name, + final AsyncExecChainHandler interceptor, + final String existing) { + this.postion = postion; + this.name = name; + this.interceptor = interceptor; + this.existing = existing; + } + + } + + private IOReactorConfig ioReactorConfig; + private H2Config h2Config; + private CharCodingConfig charCodingConfig; + private SchemePortResolver schemePortResolver; + private AuthenticationStrategy targetAuthStrategy; + private AuthenticationStrategy proxyAuthStrategy; + + private LinkedList requestInterceptors; + private LinkedList responseInterceptors; + private LinkedList execInterceptors; + + private HttpRoutePlanner routePlanner; + private RedirectStrategy redirectStrategy; + private HttpRequestRetryHandler retryHandler; + + private Lookup authSchemeRegistry; + private Lookup cookieSpecRegistry; + private CookieStore cookieStore; + private CredentialsProvider credentialsProvider; + + private String userAgent; + private Collection defaultHeaders; + private RequestConfig defaultRequestConfig; + private boolean evictIdleConnections; + private TimeValue maxIdleTime; + + private boolean systemProperties; + private boolean automaticRetriesDisabled; + private boolean redirectHandlingDisabled; + private boolean cookieManagementDisabled; + private boolean authCachingDisabled; + + private DnsResolver dnsResolver; + private TlsStrategy tlsStrategy; + + private ThreadFactory threadFactory; + + private List closeables; + + public static Http2AsyncClientBuilder create() { + return new Http2AsyncClientBuilder(); + } + + protected Http2AsyncClientBuilder() { + super(); + } + + /** + * Sets {@link H2Config} configuration. + */ + public final Http2AsyncClientBuilder setH2Config(final H2Config h2Config) { + this.h2Config = h2Config; + return this; + } + + /** + * Sets {@link IOReactorConfig} configuration. + */ + public final Http2AsyncClientBuilder setIOReactorConfig(final IOReactorConfig ioReactorConfig) { + this.ioReactorConfig = ioReactorConfig; + return this; + } + + /** + * Sets {@link CharCodingConfig} configuration. + */ + public final Http2AsyncClientBuilder setCharCodingConfig(final CharCodingConfig charCodingConfig) { + this.charCodingConfig = charCodingConfig; + return this; + } + + /** + * Assigns {@link AuthenticationStrategy} instance for target + * host authentication. + */ + public final Http2AsyncClientBuilder setTargetAuthenticationStrategy( + final AuthenticationStrategy targetAuthStrategy) { + this.targetAuthStrategy = targetAuthStrategy; + return this; + } + + /** + * Assigns {@link AuthenticationStrategy} instance for proxy + * authentication. + */ + public final Http2AsyncClientBuilder setProxyAuthenticationStrategy( + final AuthenticationStrategy proxyAuthStrategy) { + this.proxyAuthStrategy = proxyAuthStrategy; + return this; + } + + /** + * Adds this protocol interceptor to the head of the protocol processing list. + */ + public final Http2AsyncClientBuilder addRequestInterceptorFirst(final HttpResponseInterceptor interceptor) { + Args.notNull(interceptor, "Interceptor"); + if (responseInterceptors == null) { + responseInterceptors = new LinkedList<>(); + } + responseInterceptors.add(new ResponseInterceptorEntry(ResponseInterceptorEntry.Postion.FIRST, interceptor)); + return this; + } + + /** + * Adds this protocol interceptor to the tail of the protocol processing list. + */ + public final Http2AsyncClientBuilder addResponseInterceptorLast(final HttpResponseInterceptor interceptor) { + Args.notNull(interceptor, "Interceptor"); + if (responseInterceptors == null) { + responseInterceptors = new LinkedList<>(); + } + responseInterceptors.add(new ResponseInterceptorEntry(ResponseInterceptorEntry.Postion.LAST, interceptor)); + return this; + } + + /** + * Adds this execution interceptor before an existing interceptor. + */ + public final Http2AsyncClientBuilder addExecInterceptorBefore(final String existing, final String name, final AsyncExecChainHandler interceptor) { + Args.notBlank(existing, "Existing"); + Args.notBlank(name, "Name"); + Args.notNull(interceptor, "Interceptor"); + if (execInterceptors == null) { + execInterceptors = new LinkedList<>(); + } + execInterceptors.add(new ExecInterceptorEntry(ExecInterceptorEntry.Postion.BEFORE, name, interceptor, existing)); + return this; + } + + /** + * Adds this execution interceptor after interceptor with the given name. + */ + public final Http2AsyncClientBuilder addExecInterceptorAfter(final String existing, final String name, final AsyncExecChainHandler interceptor) { + Args.notBlank(existing, "Existing"); + Args.notBlank(name, "Name"); + Args.notNull(interceptor, "Interceptor"); + if (execInterceptors == null) { + execInterceptors = new LinkedList<>(); + } + execInterceptors.add(new ExecInterceptorEntry(ExecInterceptorEntry.Postion.AFTER, name, interceptor, existing)); + return this; + } + + /** + * Replace an existing interceptor with the given name with new interceptor. + */ + public final Http2AsyncClientBuilder replaceExecInterceptor(final String existing, final AsyncExecChainHandler interceptor) { + Args.notBlank(existing, "Existing"); + Args.notNull(interceptor, "Interceptor"); + if (execInterceptors == null) { + execInterceptors = new LinkedList<>(); + } + execInterceptors.add(new ExecInterceptorEntry(ExecInterceptorEntry.Postion.REPLACE, existing, interceptor, existing)); + return this; + } + + /** + * Add an interceptor to the head of the processing list. + */ + public final Http2AsyncClientBuilder addExecInterceptorFirst(final String name, final AsyncExecChainHandler interceptor) { + Args.notNull(name, "Name"); + Args.notNull(interceptor, "Interceptor"); + execInterceptors.add(new ExecInterceptorEntry(ExecInterceptorEntry.Postion.FIRST, name, interceptor, null)); + return this; + } + + /** + * Add an interceptor to the tail of the processing list. + */ + public final Http2AsyncClientBuilder addExecInterceptorLast(final String name, final AsyncExecChainHandler interceptor) { + Args.notNull(name, "Name"); + Args.notNull(interceptor, "Interceptor"); + execInterceptors.add(new ExecInterceptorEntry(ExecInterceptorEntry.Postion.LAST, name, interceptor, null)); + return this; + } + + /** + * Adds this protocol interceptor to the head of the protocol processing list. + */ + public final Http2AsyncClientBuilder addRequestInterceptorFirst(final HttpRequestInterceptor interceptor) { + Args.notNull(interceptor, "Interceptor"); + if (requestInterceptors == null) { + requestInterceptors = new LinkedList<>(); + } + requestInterceptors.add(new RequestInterceptorEntry(RequestInterceptorEntry.Postion.FIRST, interceptor)); + return this; + } + + /** + * Adds this protocol interceptor to the tail of the protocol processing list. + */ + public final Http2AsyncClientBuilder addResponseInterceptorLast(final HttpRequestInterceptor interceptor) { + Args.notNull(interceptor, "Interceptor"); + if (requestInterceptors == null) { + requestInterceptors = new LinkedList<>(); + } + requestInterceptors.add(new RequestInterceptorEntry(RequestInterceptorEntry.Postion.LAST, interceptor)); + return this; + } + + /** + * Assigns {@link HttpRequestRetryHandler} instance. + *

+ * Please note this value can be overridden by the {@link #disableAutomaticRetries()} + * method. + */ + public final Http2AsyncClientBuilder setRetryHandler(final HttpRequestRetryHandler retryHandler) { + this.retryHandler = retryHandler; + return this; + } + + /** + * Assigns {@link RedirectStrategy} instance. + *

+ * Please note this value can be overridden by the {@link #disableRedirectHandling()} + * method. + *

+ */ + public Http2AsyncClientBuilder setRedirectStrategy(final RedirectStrategy redirectStrategy) { + this.redirectStrategy = redirectStrategy; + return this; + } + + /** + * Assigns {@link SchemePortResolver} instance. + */ + public final Http2AsyncClientBuilder setSchemePortResolver(final SchemePortResolver schemePortResolver) { + this.schemePortResolver = schemePortResolver; + return this; + } + + /** + * Assigns {@link DnsResolver} instance. + */ + public final Http2AsyncClientBuilder setDnsResolver(final DnsResolver dnsResolver) { + this.dnsResolver = dnsResolver; + return this; + } + + /** + * Assigns {@link TlsStrategy} instance. + */ + public final Http2AsyncClientBuilder setTlsStrategy(final TlsStrategy tlsStrategy) { + this.tlsStrategy = tlsStrategy; + return this; + } + + /** + * Assigns {@link ThreadFactory} instance. + */ + public final Http2AsyncClientBuilder setThreadFactory(final ThreadFactory threadFactory) { + this.threadFactory = threadFactory; + return this; + } + + /** + * Assigns {@code User-Agent} value. + */ + public final Http2AsyncClientBuilder setUserAgent(final String userAgent) { + this.userAgent = userAgent; + return this; + } + + /** + * Assigns default request header values. + */ + public final Http2AsyncClientBuilder setDefaultHeaders(final Collection defaultHeaders) { + this.defaultHeaders = defaultHeaders; + return this; + } + + /** + * Assigns {@link HttpRoutePlanner} instance. + */ + public final Http2AsyncClientBuilder setRoutePlanner(final HttpRoutePlanner routePlanner) { + this.routePlanner = routePlanner; + return this; + } + + /** + * Assigns default {@link CredentialsProvider} instance which will be used + * for request execution if not explicitly set in the client execution + * context. + */ + public final Http2AsyncClientBuilder setDefaultCredentialsProvider(final CredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + return this; + } + + /** + * Assigns default {@link org.apache.hc.client5.http.auth.AuthScheme} registry which will + * be used for request execution if not explicitly set in the client execution + * context. + */ + public final Http2AsyncClientBuilder setDefaultAuthSchemeRegistry(final Lookup authSchemeRegistry) { + this.authSchemeRegistry = authSchemeRegistry; + return this; + } + + /** + * Assigns default {@link org.apache.hc.client5.http.cookie.CookieSpec} registry + * which will be used for request execution if not explicitly set in the client + * execution context. + */ + public final Http2AsyncClientBuilder setDefaultCookieSpecRegistry(final Lookup cookieSpecRegistry) { + this.cookieSpecRegistry = cookieSpecRegistry; + return this; + } + + /** + * Assigns default {@link CookieStore} instance which will be used for + * request execution if not explicitly set in the client execution context. + */ + public final Http2AsyncClientBuilder setDefaultCookieStore(final CookieStore cookieStore) { + this.cookieStore = cookieStore; + return this; + } + + /** + * Assigns default {@link RequestConfig} instance which will be used + * for request execution if not explicitly set in the client execution + * context. + */ + public final Http2AsyncClientBuilder setDefaultRequestConfig(final RequestConfig config) { + this.defaultRequestConfig = config; + return this; + } + + /** + * Use system properties when creating and configuring default + * implementations. + */ + public final Http2AsyncClientBuilder useSystemProperties() { + this.systemProperties = true; + return this; + } + + /** + * Disables automatic redirect handling. + */ + public final Http2AsyncClientBuilder disableRedirectHandling() { + redirectHandlingDisabled = true; + return this; + } + + /** + * Disables automatic request recovery and re-execution. + */ + public final Http2AsyncClientBuilder disableAutomaticRetries() { + automaticRetriesDisabled = true; + return this; + } + + /** + * Disables state (cookie) management. + */ + public final Http2AsyncClientBuilder disableCookieManagement() { + this.cookieManagementDisabled = true; + return this; + } + + /** + * Disables authentication scheme caching. + */ + public final Http2AsyncClientBuilder disableAuthCaching() { + this.authCachingDisabled = true; + return this; + } + + /** + * Makes this instance of HttpClient proactively evict idle connections from the + * connection pool using a background thread. + *

+ * One MUST explicitly close HttpClient with {@link CloseableHttpAsyncClient#close()} + * in order to stop and release the background thread. + *

+ * Please note this method has no effect if the instance of HttpClient is configuted to + * use a shared connection manager. + * + * @param maxIdleTime maximum time persistent connections can stay idle while kept alive + * in the connection pool. Connections whose inactivity period exceeds this value will + * get closed and evicted from the pool. + */ + public final Http2AsyncClientBuilder evictIdleConnections(final TimeValue maxIdleTime) { + this.evictIdleConnections = true; + this.maxIdleTime = maxIdleTime; + return this; + } + + /** + * Request exec chain customization and extension. + *

+ * For internal use. + */ + @Internal + protected void customizeExecChain(final NamedElementChain execChainDefinition) { + } + + /** + * Adds to the list of {@link Closeable} resources to be managed by the client. + *

+ * For internal use. + */ + @Internal + protected void addCloseable(final Closeable closeable) { + if (closeable == null) { + return; + } + if (closeables == null) { + closeables = new ArrayList<>(); + } + closeables.add(closeable); + } + + public CloseableHttpAsyncClient build() { + final NamedElementChain execChainDefinition = new NamedElementChain<>(); + execChainDefinition.addLast( + new Http2AsyncMainClientExec(), + ChainElements.MAIN_TRANSPORT.name()); + + AuthenticationStrategy targetAuthStrategyCopy = this.targetAuthStrategy; + if (targetAuthStrategyCopy == null) { + targetAuthStrategyCopy = DefaultAuthenticationStrategy.INSTANCE; + } + AuthenticationStrategy proxyAuthStrategyCopy = this.proxyAuthStrategy; + if (proxyAuthStrategyCopy == null) { + proxyAuthStrategyCopy = DefaultAuthenticationStrategy.INSTANCE; + } + + String userAgentCopy = this.userAgent; + if (userAgentCopy == null) { + if (systemProperties) { + userAgentCopy = getProperty("http.agent", null); + } + if (userAgentCopy == null) { + userAgentCopy = VersionInfo.getSoftwareInfo("Apache-HttpAsyncClient", + "org.apache.hc.client5", getClass()); + } + } + + execChainDefinition.addFirst( + new AsyncConnectExec( + new DefaultHttpProcessor(new RequestTargetHost(), new RequestUserAgent(userAgentCopy)), + proxyAuthStrategyCopy), + ChainElements.CONNECT.name()); + + final HttpProcessorBuilder b = HttpProcessorBuilder.create(); + if (requestInterceptors != null) { + for (final RequestInterceptorEntry entry: requestInterceptors) { + if (entry.postion == RequestInterceptorEntry.Postion.FIRST) { + b.addFirst(entry.interceptor); + } + } + } + if (responseInterceptors != null) { + for (final ResponseInterceptorEntry entry: responseInterceptors) { + if (entry.postion == ResponseInterceptorEntry.Postion.FIRST) { + b.addFirst(entry.interceptor); + } + } + } + b.addAll( + new RequestDefaultHeaders(defaultHeaders), + new RequestUserAgent(userAgentCopy), + new RequestExpectContinue()); + if (!cookieManagementDisabled) { + b.add(new RequestAddCookies()); + } + if (!authCachingDisabled) { + b.add(new RequestAuthCache()); + } + if (!cookieManagementDisabled) { + b.add(new ResponseProcessCookies()); + } + if (requestInterceptors != null) { + for (final RequestInterceptorEntry entry: requestInterceptors) { + if (entry.postion == RequestInterceptorEntry.Postion.LAST) { + b.addFirst(entry.interceptor); + } + } + } + if (responseInterceptors != null) { + for (final ResponseInterceptorEntry entry: responseInterceptors) { + if (entry.postion == ResponseInterceptorEntry.Postion.LAST) { + b.addFirst(entry.interceptor); + } + } + } + + final HttpProcessor httpProcessor = b.build(); + execChainDefinition.addFirst( + new AsyncProtocolExec(httpProcessor, targetAuthStrategyCopy, proxyAuthStrategyCopy), + ChainElements.PROTOCOL.name()); + + // Add request retry executor, if not disabled + if (!automaticRetriesDisabled) { + HttpRequestRetryHandler retryHandlerCopy = this.retryHandler; + if (retryHandlerCopy == null) { + retryHandlerCopy = DefaultHttpRequestRetryHandler.INSTANCE; + } + execChainDefinition.addFirst( + new AsyncRetryExec(retryHandlerCopy), + ChainElements.RETRY_IO_ERROR.name()); + } + + HttpRoutePlanner routePlannerCopy = this.routePlanner; + if (routePlannerCopy == null) { + SchemePortResolver schemePortResolverCopy = this.schemePortResolver; + if (schemePortResolverCopy == null) { + schemePortResolverCopy = DefaultSchemePortResolver.INSTANCE; + } + routePlannerCopy = new DefaultRoutePlanner(schemePortResolverCopy); + } + + // Add redirect executor, if not disabled + if (!redirectHandlingDisabled) { + RedirectStrategy redirectStrategyCopy = this.redirectStrategy; + if (redirectStrategyCopy == null) { + redirectStrategyCopy = DefaultRedirectStrategy.INSTANCE; + } + execChainDefinition.addFirst( + new AsyncRedirectExec(routePlannerCopy, redirectStrategyCopy), + ChainElements.REDIRECT.name()); + } + + final AsyncPushConsumerRegistry pushConsumerRegistry = new AsyncPushConsumerRegistry(); + final IOEventHandlerFactory ioEventHandlerFactory = new Http2AsyncClientEventHandlerFactory( + new DefaultHttpProcessor(new H2RequestContent(), new H2RequestTargetHost(), new H2RequestConnControl()), + new HandlerFactory() { + + @Override + public AsyncPushConsumer create(final HttpRequest request, final HttpContext context) throws HttpException { + return pushConsumerRegistry.get(request); + } + + }, + h2Config != null ? h2Config : H2Config.DEFAULT, + charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT); + final DefaultConnectingIOReactor ioReactor = new DefaultConnectingIOReactor( + ioEventHandlerFactory, + ioReactorConfig != null ? ioReactorConfig : IOReactorConfig.DEFAULT, + threadFactory != null ? threadFactory : new DefaultThreadFactory("httpclient-dispatch", true), + null, + null, + new Callback() { + + @Override + public void execute(final IOSession ioSession) { + ioSession.addFirst(new ShutdownCommand(ShutdownType.GRACEFUL)); + } + + }); + + if (execInterceptors != null) { + for (final ExecInterceptorEntry entry: execInterceptors) { + switch (entry.postion) { + case AFTER: + execChainDefinition.addAfter(entry.existing, entry.interceptor, entry.name); + break; + case BEFORE: + execChainDefinition.addBefore(entry.existing, entry.interceptor, entry.name); + break; + case FIRST: + execChainDefinition.addFirst(entry.interceptor, entry.name); + break; + case LAST: + execChainDefinition.addLast(entry.interceptor, entry.name); + break; + } + } + } + + customizeExecChain(execChainDefinition); + + NamedElementChain.Node current = execChainDefinition.getLast(); + AsyncExecChainElement execChain = null; + while (current != null) { + execChain = new AsyncExecChainElement(current.getValue(), execChain); + current = current.getPrevious(); + } + + Lookup authSchemeRegistryCopy = this.authSchemeRegistry; + if (authSchemeRegistryCopy == null) { + authSchemeRegistryCopy = RegistryBuilder.create() + .register(AuthSchemes.BASIC, new BasicSchemeFactory()) + .register(AuthSchemes.DIGEST, new DigestSchemeFactory()) + .register(AuthSchemes.CREDSSP, new CredSspSchemeFactory()) + .register(AuthSchemes.NTLM, new NTLMSchemeFactory()) + .register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(KerberosConfig.DEFAULT, SystemDefaultDnsResolver.INSTANCE)) + .register(AuthSchemes.KERBEROS, new KerberosSchemeFactory(KerberosConfig.DEFAULT, SystemDefaultDnsResolver.INSTANCE)) + .build(); + } + Lookup cookieSpecRegistryCopy = this.cookieSpecRegistry; + if (cookieSpecRegistryCopy == null) { + cookieSpecRegistryCopy = CookieSpecRegistries.createDefault(); + } + + CookieStore cookieStoreCopy = this.cookieStore; + if (cookieStoreCopy == null) { + cookieStoreCopy = new BasicCookieStore(); + } + + CredentialsProvider credentialsProviderCopy = this.credentialsProvider; + if (credentialsProviderCopy == null) { + if (systemProperties) { + credentialsProviderCopy = new SystemDefaultCredentialsProvider(); + } else { + credentialsProviderCopy = new BasicCredentialsProvider(); + } + } + + TlsStrategy tlsStrategyCopy = this.tlsStrategy; + if (tlsStrategyCopy == null) { + if (systemProperties) { + tlsStrategyCopy = H2TlsStrategy.getSystemDefault(); + } else { + tlsStrategyCopy = H2TlsStrategy.getDefault(); + } + } + + final MultuhomeConnectionInitiator connectionInitiator = new MultuhomeConnectionInitiator(ioReactor, dnsResolver); + final H2ConnPool connPool = new H2ConnPool(connectionInitiator, new Resolver() { + + @Override + public InetSocketAddress resolve(final HttpHost host) { + return null; + } + + }, tlsStrategyCopy); + + List closeablesCopy = closeables != null ? new ArrayList<>(closeables) : null; + if (closeablesCopy == null) { + closeablesCopy = new ArrayList<>(1); + } + if (evictIdleConnections) { + final IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor(connPool, + maxIdleTime != null ? maxIdleTime : TimeValue.ofSeconds(30L)); + closeablesCopy.add(new Closeable() { + + @Override + public void close() throws IOException { + connectionEvictor.shutdown(); + } + + }); + connectionEvictor.start(); + } + closeablesCopy.add(connPool); + + return new InternalHttp2AsyncClient( + ioReactor, + execChain, + pushConsumerRegistry, + threadFactory != null ? threadFactory : new DefaultThreadFactory("httpclient-main", true), + connPool, + routePlannerCopy, + cookieSpecRegistryCopy, + authSchemeRegistryCopy, + cookieStoreCopy, + credentialsProviderCopy, + defaultRequestConfig, + closeablesCopy); + } + + private static String getProperty(final String key, final String defaultValue) { + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public String run() { + return System.getProperty(key, defaultValue); + } + }); + } + + static class IdleConnectionEvictor implements Closeable { + + private final Thread thread; + + public IdleConnectionEvictor(final H2ConnPool connPool, final TimeValue maxIdleTime) { + this.thread = new DefaultThreadFactory("idle-connection-evictor", true).newThread(new Runnable() { + @Override + public void run() { + try { + while (!Thread.currentThread().isInterrupted()) { + Thread.sleep(maxIdleTime.toMillis()); + connPool.closeIdle(maxIdleTime); + } + } catch (final InterruptedException ex) { + Thread.currentThread().interrupt(); + } catch (final Exception ex) { + } + + } + }); + } + + public void start() { + thread.start(); + } + + public void shutdown() { + thread.interrupt(); + } + + @Override + public void close() throws IOException { + shutdown(); + } + + } + +} diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncClientEventHandlerFactory.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncClientEventHandlerFactory.java new file mode 100644 index 000000000..52f399ce8 --- /dev/null +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncClientEventHandlerFactory.java @@ -0,0 +1,194 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.hc.client5.http.impl.async; + +import java.io.IOException; +import java.util.List; + +import org.apache.hc.client5.http.impl.ConnPoolSupport; +import org.apache.hc.core5.annotation.Contract; +import org.apache.hc.core5.annotation.ThreadingBehavior; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpConnection; +import org.apache.hc.core5.http.config.CharCodingConfig; +import org.apache.hc.core5.http.nio.AsyncPushConsumer; +import org.apache.hc.core5.http.nio.HandlerFactory; +import org.apache.hc.core5.http.protocol.HttpProcessor; +import org.apache.hc.core5.http2.config.H2Config; +import org.apache.hc.core5.http2.frame.FramePrinter; +import org.apache.hc.core5.http2.frame.RawFrame; +import org.apache.hc.core5.http2.impl.nio.ClientHttp2StreamMultiplexerFactory; +import org.apache.hc.core5.http2.impl.nio.Http2OnlyClientProtocolNegotiator; +import org.apache.hc.core5.http2.impl.nio.Http2StreamListener; +import org.apache.hc.core5.reactor.IOEventHandler; +import org.apache.hc.core5.reactor.IOEventHandlerFactory; +import org.apache.hc.core5.reactor.TlsCapableIOSession; +import org.apache.hc.core5.util.Args; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * @since 5.0 + */ +@Contract(threading = ThreadingBehavior.IMMUTABLE) +class Http2AsyncClientEventHandlerFactory implements IOEventHandlerFactory { + + private final Logger wireLog = LogManager.getLogger("org.apache.hc.client5.http.wire"); + private final Logger headerLog = LogManager.getLogger("org.apache.hc.client5.http.headers"); + private final Logger frameLog = LogManager.getLogger("org.apache.hc.client5.http2.frame"); + private final Logger framePayloadLog = LogManager.getLogger("org.apache.hc.client5.http2.frame.payload"); + private final Logger flowCtrlLog = LogManager.getLogger("org.apache.hc.client5.http2.flow"); + + private final HttpProcessor httpProcessor; + private final HandlerFactory exchangeHandlerFactory; + private final H2Config h2Config; + private final CharCodingConfig charCodingConfig; + + Http2AsyncClientEventHandlerFactory( + final HttpProcessor httpProcessor, + final HandlerFactory exchangeHandlerFactory, + final H2Config h2Config, + final CharCodingConfig charCodingConfig) { + this.httpProcessor = Args.notNull(httpProcessor, "HTTP processor"); + this.exchangeHandlerFactory = exchangeHandlerFactory; + this.h2Config = h2Config != null ? h2Config : H2Config.DEFAULT; + this.charCodingConfig = charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT; + } + + @Override + public IOEventHandler createHandler(final TlsCapableIOSession ioSession, final Object attachment) { + final Logger sessionLog = LogManager.getLogger(ioSession.getClass()); + if (sessionLog.isDebugEnabled() + || wireLog.isDebugEnabled() + || headerLog.isDebugEnabled() + || frameLog.isDebugEnabled() + || framePayloadLog.isDebugEnabled() + || flowCtrlLog.isDebugEnabled()) { + final String id = ConnPoolSupport.getId(ioSession); + final ClientHttp2StreamMultiplexerFactory http2StreamHandlerFactory = new ClientHttp2StreamMultiplexerFactory( + httpProcessor, + exchangeHandlerFactory, + h2Config, + charCodingConfig, + new Http2StreamListener() { + + final FramePrinter framePrinter = new FramePrinter(); + + private void logFrameInfo(final String prefix, final RawFrame frame) { + try { + final LogAppendable logAppendable = new LogAppendable(frameLog, prefix); + framePrinter.printFrameInfo(frame, logAppendable); + logAppendable.flush(); + } catch (final IOException ignore) { + } + } + + private void logFramePayload(final String prefix, final RawFrame frame) { + try { + final LogAppendable logAppendable = new LogAppendable(framePayloadLog, prefix); + framePrinter.printPayload(frame, logAppendable); + logAppendable.flush(); + } catch (final IOException ignore) { + } + } + + private void logFlowControl(final String prefix, final int streamId, final int delta, final int actualSize) { + final StringBuilder buffer = new StringBuilder(); + buffer.append(prefix).append(" stream ").append(streamId).append(" flow control " ) + .append(delta).append(" -> ") + .append(actualSize); + flowCtrlLog.debug(buffer.toString()); + } + + @Override + public void onHeaderInput(final HttpConnection connection, final int streamId, final List headers) { + if (headerLog.isDebugEnabled()) { + for (int i = 0; i < headers.size(); i++) { + headerLog.debug(id + " << " + headers.get(i)); + } + } + } + + @Override + public void onHeaderOutput(final HttpConnection connection, final int streamId, final List headers) { + if (headerLog.isDebugEnabled()) { + for (int i = 0; i < headers.size(); i++) { + headerLog.debug(id + " >> " + headers.get(i)); + } + } + } + + @Override + public void onFrameInput(final HttpConnection connection, final int streamId, final RawFrame frame) { + if (frameLog.isDebugEnabled()) { + logFrameInfo(id + " <<", frame); + } + if (framePayloadLog.isDebugEnabled()) { + logFramePayload(id + " <<", frame); + } + } + + @Override + public void onFrameOutput(final HttpConnection connection, final int streamId, final RawFrame frame) { + if (frameLog.isDebugEnabled()) { + logFrameInfo(id + " >>", frame); + } + if (framePayloadLog.isDebugEnabled()) { + logFramePayload(id + " >>", frame); + } + } + + @Override + public void onInputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { + if (flowCtrlLog.isDebugEnabled()) { + logFlowControl(id + " <<", streamId, delta, actualSize); + } + } + + @Override + public void onOutputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) { + if (flowCtrlLog.isDebugEnabled()) { + logFlowControl(id + " >>", streamId, delta, actualSize); + } + } + + }); + final LoggingIOSession loggingIOSession = new LoggingIOSession(ioSession, id, sessionLog, wireLog); + return new Http2OnlyClientProtocolNegotiator(loggingIOSession, http2StreamHandlerFactory); + } else { + final ClientHttp2StreamMultiplexerFactory http2StreamHandlerFactory = new ClientHttp2StreamMultiplexerFactory( + httpProcessor, + exchangeHandlerFactory, + h2Config, + charCodingConfig, + null); + return new Http2OnlyClientProtocolNegotiator(ioSession, http2StreamHandlerFactory); + } + } + +} diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncMainClientExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncMainClientExec.java new file mode 100644 index 000000000..a954a568a --- /dev/null +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncMainClientExec.java @@ -0,0 +1,170 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.hc.client5.http.impl.async; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.hc.client5.http.HttpRoute; +import org.apache.hc.client5.http.async.AsyncExecCallback; +import org.apache.hc.client5.http.async.AsyncExecChain; +import org.apache.hc.client5.http.async.AsyncExecChainHandler; +import org.apache.hc.client5.http.async.AsyncExecRuntime; +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.core5.http.EntityDetails; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.message.RequestLine; +import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; +import org.apache.hc.core5.http.nio.AsyncDataConsumer; +import org.apache.hc.core5.http.nio.AsyncEntityProducer; +import org.apache.hc.core5.http.nio.CapacityChannel; +import org.apache.hc.core5.http.nio.DataStreamChannel; +import org.apache.hc.core5.http.nio.RequestChannel; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +class Http2AsyncMainClientExec implements AsyncExecChainHandler { + + private final Logger log = LogManager.getLogger(getClass()); + + @Override + public void execute( + final HttpRequest request, + final AsyncEntityProducer entityProducer, + final AsyncExecChain.Scope scope, + final AsyncExecChain chain, + final AsyncExecCallback asyncExecCallback) throws HttpException, IOException { + final String exchangeId = scope.exchangeId; + final HttpRoute route = scope.route; + final HttpClientContext clientContext = scope.clientContext; + final AsyncExecRuntime execRuntime = scope.execRuntime; + + if (log.isDebugEnabled()) { + log.debug(exchangeId + ": executing " + new RequestLine(request)); + } + + final AsyncClientExchangeHandler internalExchangeHandler = new AsyncClientExchangeHandler() { + + private final AtomicReference entityConsumerRef = new AtomicReference<>(null); + + @Override + public void releaseResources() { + final AsyncDataConsumer entityConsumer = entityConsumerRef.getAndSet(null); + if (entityConsumer != null) { + entityConsumer.releaseResources(); + } + } + + @Override + public void failed(final Exception cause) { + execRuntime.markConnectionNonReusable(); + asyncExecCallback.failed(cause); + } + + @Override + public void cancel() { + failed(new InterruptedIOException()); + } + + @Override + public void produceRequest(final RequestChannel channel) throws HttpException, IOException { + channel.sendRequest(request, entityProducer); + } + + @Override + public int available() { + return entityProducer.available(); + } + + @Override + public void produce(final DataStreamChannel channel) throws IOException { + entityProducer.produce(channel); + } + + @Override + public void consumeInformation(final HttpResponse response) throws HttpException, IOException { + } + + @Override + public void consumeResponse(final HttpResponse response, final EntityDetails entityDetails) throws HttpException, IOException { + entityConsumerRef.set(asyncExecCallback.handleResponse(response, entityDetails)); + if (entityDetails == null) { + execRuntime.validateConnection(); + asyncExecCallback.completed(); + } + } + + @Override + public void updateCapacity(final CapacityChannel capacityChannel) throws IOException { + final AsyncDataConsumer entityConsumer = entityConsumerRef.get(); + if (entityConsumer != null) { + entityConsumer.updateCapacity(capacityChannel); + } else { + capacityChannel.update(Integer.MAX_VALUE); + } + } + + @Override + public int consume(final ByteBuffer src) throws IOException { + final AsyncDataConsumer entityConsumer = entityConsumerRef.get(); + if (entityConsumer != null) { + return entityConsumer.consume(src); + } else { + return Integer.MAX_VALUE; + } + } + + @Override + public void streamEnd(final List trailers) throws HttpException, IOException { + final AsyncDataConsumer entityConsumer = entityConsumerRef.getAndSet(null); + if (entityConsumer != null) { + entityConsumer.streamEnd(trailers); + } else { + execRuntime.validateConnection(); + } + asyncExecCallback.completed(); + } + + }; + + if (log.isDebugEnabled()) { + execRuntime.execute( + new LoggingAsyncClientExchangeHandler(log, exchangeId, internalExchangeHandler), + clientContext); + } else { + execRuntime.execute( + internalExchangeHandler, clientContext); + } + } + +} diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java index 87ce29494..91c28ae9c 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java @@ -736,7 +736,7 @@ public class HttpAsyncClientBuilder { final NamedElementChain execChainDefinition = new NamedElementChain<>(); execChainDefinition.addLast( - new AsyncMainClientExec(keepAliveStrategyCopy, userTokenHandlerCopy), + new HttpAsyncMainClientExec(keepAliveStrategyCopy, userTokenHandlerCopy), ChainElements.MAIN_TRANSPORT.name()); AuthenticationStrategy targetAuthStrategyCopy = this.targetAuthStrategy; diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientEventHandlerFactory.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientEventHandlerFactory.java index 95d9267ce..4a97d67b1 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientEventHandlerFactory.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientEventHandlerFactory.java @@ -71,7 +71,7 @@ import org.apache.logging.log4j.Logger; * @since 5.0 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) -public class HttpAsyncClientEventHandlerFactory implements IOEventHandlerFactory { +class HttpAsyncClientEventHandlerFactory implements IOEventHandlerFactory { private final Logger streamLog = LogManager.getLogger(InternalHttpAsyncClient.class); private final Logger wireLog = LogManager.getLogger("org.apache.hc.client5.http.wire"); diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClients.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClients.java index 56a59c346..dab74fd04 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClients.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClients.java @@ -88,6 +88,31 @@ public class HttpAsyncClients { return HttpAsyncClientBuilder.create().useSystemProperties().build(); } + /** + * Creates builder object for construction of custom HTTP/2 + * {@link CloseableHttpAsyncClient} instances optimized for HTTP/2 protocol + * and message multiplexing + */ + public static Http2AsyncClientBuilder customHttp2() { + return Http2AsyncClientBuilder.create(); + } + + /** + * Creates HTTP/2 {@link CloseableHttpAsyncClient} instance with default configuration + * optimized for HTTP/2 protocol and message multiplexing. + */ + public static CloseableHttpAsyncClient createHttp2Default() { + return Http2AsyncClientBuilder.create().build(); + } + + /** + * Creates HTTP/2 {@link CloseableHttpAsyncClient} instance with default configuration and + * system properties optimized for HTTP/2 protocol and message multiplexing. + */ + public static CloseableHttpAsyncClient createHttp2System() { + return Http2AsyncClientBuilder.create().useSystemProperties().build(); + } + private static HttpProcessor createMinimalProtocolProcessor() { return new DefaultHttpProcessor( new H2RequestContent(), @@ -220,7 +245,7 @@ public class HttpAsyncClients { final TlsStrategy tlsStrategy) { final AsyncPushConsumerRegistry pushConsumerRegistry = new AsyncPushConsumerRegistry(); return createMinimalHttp2AsyncClientImpl( - new HttpAsyncClientEventHandlerFactory( + new Http2AsyncClientEventHandlerFactory( createMinimalProtocolProcessor(), new HandlerFactory() { @@ -230,11 +255,8 @@ public class HttpAsyncClients { } }, - HttpVersionPolicy.FORCE_HTTP_2, h2Config, - null, - CharCodingConfig.DEFAULT, - DefaultConnectionReuseStrategy.INSTANCE), + CharCodingConfig.DEFAULT), pushConsumerRegistry, ioReactorConfig, dnsResolver, diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncMainClientExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncMainClientExec.java similarity index 98% rename from httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncMainClientExec.java rename to httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncMainClientExec.java index d23f20f08..012803c62 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncMainClientExec.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncMainClientExec.java @@ -58,14 +58,14 @@ import org.apache.hc.core5.util.TimeValue; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -class AsyncMainClientExec implements AsyncExecChainHandler { +class HttpAsyncMainClientExec implements AsyncExecChainHandler { private final Logger log = LogManager.getLogger(getClass()); private final ConnectionKeepAliveStrategy keepAliveStrategy; private final UserTokenHandler userTokenHandler; - AsyncMainClientExec(final ConnectionKeepAliveStrategy keepAliveStrategy, final UserTokenHandler userTokenHandler) { + HttpAsyncMainClientExec(final ConnectionKeepAliveStrategy keepAliveStrategy, final UserTokenHandler userTokenHandler) { this.keepAliveStrategy = keepAliveStrategy; this.userTokenHandler = userTokenHandler; } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalAbstractHttpAsyncClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalAbstractHttpAsyncClient.java new file mode 100644 index 000000000..aab6f7816 --- /dev/null +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalAbstractHttpAsyncClient.java @@ -0,0 +1,308 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.hc.client5.http.impl.async; + +import java.io.Closeable; +import java.io.IOException; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.hc.client5.http.HttpRoute; +import org.apache.hc.client5.http.async.AsyncExecCallback; +import org.apache.hc.client5.http.async.AsyncExecChain; +import org.apache.hc.client5.http.async.AsyncExecRuntime; +import org.apache.hc.client5.http.async.methods.SimpleRequestProducer; +import org.apache.hc.client5.http.auth.AuthSchemeProvider; +import org.apache.hc.client5.http.auth.CredentialsProvider; +import org.apache.hc.client5.http.config.Configurable; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.cookie.CookieSpecProvider; +import org.apache.hc.client5.http.cookie.CookieStore; +import org.apache.hc.client5.http.impl.ExecSupport; +import org.apache.hc.client5.http.impl.RequestCopier; +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.core5.concurrent.BasicFuture; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.EntityDetails; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.config.Lookup; +import org.apache.hc.core5.http.nio.AsyncDataConsumer; +import org.apache.hc.core5.http.nio.AsyncEntityProducer; +import org.apache.hc.core5.http.nio.AsyncRequestProducer; +import org.apache.hc.core5.http.nio.AsyncResponseConsumer; +import org.apache.hc.core5.http.nio.DataStreamChannel; +import org.apache.hc.core5.http.nio.RequestChannel; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.reactor.DefaultConnectingIOReactor; + +abstract class InternalAbstractHttpAsyncClient extends AbstractHttpAsyncClientBase { + + private final AsyncExecChainElement execChain; + private final Lookup cookieSpecRegistry; + private final Lookup authSchemeRegistry; + private final CookieStore cookieStore; + private final CredentialsProvider credentialsProvider; + private final RequestConfig defaultConfig; + private final List closeables; + + InternalAbstractHttpAsyncClient( + final DefaultConnectingIOReactor ioReactor, + final AsyncPushConsumerRegistry pushConsumerRegistry, + final ThreadFactory threadFactory, + final AsyncExecChainElement execChain, + final Lookup cookieSpecRegistry, + final Lookup authSchemeRegistry, + final CookieStore cookieStore, + final CredentialsProvider credentialsProvider, + final RequestConfig defaultConfig, + final List closeables) { + super(ioReactor, pushConsumerRegistry, threadFactory); + this.execChain = execChain; + this.cookieSpecRegistry = cookieSpecRegistry; + this.authSchemeRegistry = authSchemeRegistry; + this.cookieStore = cookieStore; + this.credentialsProvider = credentialsProvider; + this.defaultConfig = defaultConfig; + this.closeables = closeables; + } + + @Override + public void close() { + super.close(); + if (closeables != null) { + for (final Closeable closeable: closeables) { + try { + closeable.close(); + } catch (final IOException ex) { + log.error(ex.getMessage(), ex); + } + } + } + } + + private void setupContext(final HttpClientContext context) { + if (context.getAttribute(HttpClientContext.AUTHSCHEME_REGISTRY) == null) { + context.setAttribute(HttpClientContext.AUTHSCHEME_REGISTRY, authSchemeRegistry); + } + if (context.getAttribute(HttpClientContext.COOKIESPEC_REGISTRY) == null) { + context.setAttribute(HttpClientContext.COOKIESPEC_REGISTRY, cookieSpecRegistry); + } + if (context.getAttribute(HttpClientContext.COOKIE_STORE) == null) { + context.setAttribute(HttpClientContext.COOKIE_STORE, cookieStore); + } + if (context.getAttribute(HttpClientContext.CREDS_PROVIDER) == null) { + context.setAttribute(HttpClientContext.CREDS_PROVIDER, credentialsProvider); + } + if (context.getAttribute(HttpClientContext.REQUEST_CONFIG) == null) { + context.setAttribute(HttpClientContext.REQUEST_CONFIG, defaultConfig); + } + } + + abstract AsyncExecRuntime crerateAsyncExecRuntime(); + + abstract HttpRoute determineRoute(HttpRequest request, HttpClientContext clientContext) throws HttpException; + + @Override + public Future execute( + final AsyncRequestProducer requestProducer, + final AsyncResponseConsumer responseConsumer, + final HttpContext context, + final FutureCallback callback) { + ensureRunning(); + final BasicFuture future = new BasicFuture<>(callback); + try { + final HttpClientContext clientContext = HttpClientContext.adapt(context); + requestProducer.sendRequest(new RequestChannel() { + + @Override + public void sendRequest( + final HttpRequest request, + final EntityDetails entityDetails) throws HttpException, IOException { + + RequestConfig requestConfig = null; + if (request instanceof Configurable) { + requestConfig = ((Configurable) request).getConfig(); + } + if (requestConfig != null) { + clientContext.setRequestConfig(requestConfig); + } + final HttpRoute route = determineRoute(request, clientContext); + final String exchangeId = String.format("ex-%08X", ExecSupport.getNextExecNumber()); + final AsyncExecRuntime execRuntime = crerateAsyncExecRuntime(); + if (log.isDebugEnabled()) { + log.debug(exchangeId + ": preparing request execution"); + } + + setupContext(clientContext); + + final AsyncExecChain.Scope scope = new AsyncExecChain.Scope(exchangeId, route, request, clientContext, execRuntime); + final AtomicReference resultRef = new AtomicReference<>(null); + final AtomicBoolean outputTerminated = new AtomicBoolean(false); + execChain.execute( + RequestCopier.INSTANCE.copy(request), + entityDetails != null ? new AsyncEntityProducer() { + + @Override + public void releaseResources() { + requestProducer.releaseResources(); + } + + @Override + public void failed(final Exception cause) { + requestProducer.failed(cause); + } + + @Override + public boolean isRepeatable() { + //TODO: use AsyncRequestProducer#isRepeatable once available + return requestProducer instanceof SimpleRequestProducer; + } + + @Override + public long getContentLength() { + return entityDetails.getContentLength(); + } + + @Override + public String getContentType() { + return entityDetails.getContentType(); + } + + @Override + public String getContentEncoding() { + return entityDetails.getContentEncoding(); + } + + @Override + public boolean isChunked() { + return entityDetails.isChunked(); + } + + @Override + public Set getTrailerNames() { + return entityDetails.getTrailerNames(); + } + + @Override + public int available() { + return requestProducer.available(); + } + + @Override + public void produce(final DataStreamChannel channel) throws IOException { + if (outputTerminated.get()) { + channel.endStream(); + return; + } + requestProducer.produce(channel); + } + + } : null, + scope, + new AsyncExecCallback() { + + @Override + public AsyncDataConsumer handleResponse( + final HttpResponse response, + final EntityDetails entityDetails) throws HttpException, IOException { + if (response.getCode() >= HttpStatus.SC_CLIENT_ERROR) { + outputTerminated.set(true); + requestProducer.releaseResources(); + } + responseConsumer.consumeResponse(response, entityDetails, + //TODO: eliminate this callback after upgrade to HttpCore 5.0b2 + new FutureCallback() { + + @Override + public void completed(final T result) { + resultRef.set(result); + } + + @Override + public void failed(final Exception ex) { + future.failed(ex); + } + + @Override + public void cancelled() { + future.cancel(); + } + + }); + return responseConsumer; + } + + @Override + public void completed() { + if (log.isDebugEnabled()) { + log.debug(exchangeId + ": message exchange successfully completed"); + } + try { + execRuntime.releaseConnection(); + future.completed(resultRef.getAndSet(null)); + } finally { + responseConsumer.releaseResources(); + requestProducer.releaseResources(); + } + } + + @Override + public void failed(final Exception cause) { + if (log.isDebugEnabled()) { + log.debug(exchangeId + ": request failed: " + cause.getMessage()); + } + try { + execRuntime.discardConnection(); + responseConsumer.failed(cause); + } finally { + try { + future.failed(cause); + } finally { + responseConsumer.releaseResources(); + requestProducer.releaseResources(); + } + } + } + + }); + } + + }); + } catch (final HttpException | IOException ex) { + future.failed(ex); + } + return future; + } + +} diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttp2AsyncClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttp2AsyncClient.java new file mode 100644 index 000000000..f68c69a5b --- /dev/null +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttp2AsyncClient.java @@ -0,0 +1,88 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.hc.client5.http.impl.async; + +import java.io.Closeable; +import java.util.List; +import java.util.concurrent.ThreadFactory; + +import org.apache.hc.client5.http.HttpRoute; +import org.apache.hc.client5.http.async.AsyncExecRuntime; +import org.apache.hc.client5.http.auth.AuthSchemeProvider; +import org.apache.hc.client5.http.auth.CredentialsProvider; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.cookie.CookieSpecProvider; +import org.apache.hc.client5.http.cookie.CookieStore; +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.client5.http.routing.HttpRoutePlanner; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.config.Lookup; +import org.apache.hc.core5.http2.nio.pool.H2ConnPool; +import org.apache.hc.core5.reactor.DefaultConnectingIOReactor; + +class InternalHttp2AsyncClient extends InternalAbstractHttpAsyncClient { + + private final HttpRoutePlanner routePlanner; + private final H2ConnPool connPool; + + InternalHttp2AsyncClient( + final DefaultConnectingIOReactor ioReactor, + final AsyncExecChainElement execChain, + final AsyncPushConsumerRegistry pushConsumerRegistry, + final ThreadFactory threadFactory, + final H2ConnPool connPool, + final HttpRoutePlanner routePlanner, + final Lookup cookieSpecRegistry, + final Lookup authSchemeRegistry, + final CookieStore cookieStore, + final CredentialsProvider credentialsProvider, + final RequestConfig defaultConfig, + final List closeables) { + super(ioReactor, pushConsumerRegistry, threadFactory, execChain, + cookieSpecRegistry, authSchemeRegistry, cookieStore, credentialsProvider, defaultConfig, closeables); + this.connPool = connPool; + this.routePlanner = routePlanner; + } + + @Override + AsyncExecRuntime crerateAsyncExecRuntime() { + return new InternalHttp2AsyncExecRuntime(log, connPool); + } + + @Override + HttpRoute determineRoute(final HttpRequest request, final HttpClientContext clientContext) throws HttpException { + final HttpHost target = routePlanner.determineTargetHost(request, clientContext); + final HttpRoute route = routePlanner.determineRoute(target, clientContext); + if (route.isTunnelled()) { + throw new HttpException("HTTP/2 tunneling not supported"); + } + return route; + } + +} diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttp2AsyncExecRuntime.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttp2AsyncExecRuntime.java new file mode 100644 index 000000000..624dafe80 --- /dev/null +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttp2AsyncExecRuntime.java @@ -0,0 +1,245 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.hc.client5.http.impl.async; + +import java.io.InterruptedIOException; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.hc.client5.http.HttpRoute; +import org.apache.hc.client5.http.async.AsyncExecRuntime; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.ConnPoolSupport; +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; +import org.apache.hc.core5.http.nio.command.ExecutionCommand; +import org.apache.hc.core5.http2.nio.pool.H2ConnPool; +import org.apache.hc.core5.io.ShutdownType; +import org.apache.hc.core5.reactor.IOSession; +import org.apache.hc.core5.util.TimeValue; +import org.apache.logging.log4j.Logger; + +class InternalHttp2AsyncExecRuntime implements AsyncExecRuntime { + + private final Logger log; + private final H2ConnPool connPool; + private final AtomicReference sessionRef; + private volatile boolean reusable; + + InternalHttp2AsyncExecRuntime(final Logger log, final H2ConnPool connPool) { + super(); + this.log = log; + this.connPool = connPool; + this.sessionRef = new AtomicReference<>(null); + } + + @Override + public boolean isConnectionAcquired() { + return sessionRef.get() != null; + } + + @Override + public void acquireConnection( + final HttpRoute route, + final Object object, + final HttpClientContext context, + final FutureCallback callback) { + if (sessionRef.get() == null) { + final HttpHost target = route.getTargetHost(); + final RequestConfig requestConfig = context.getRequestConfig(); + connPool.getSession(target, requestConfig.getConnectTimeout(), new FutureCallback() { + + @Override + public void completed(final IOSession ioSession) { + sessionRef.set(new Endpoint(target, ioSession)); + reusable = true; + callback.completed(InternalHttp2AsyncExecRuntime.this); + } + + @Override + public void failed(final Exception ex) { + callback.failed(ex); + } + + @Override + public void cancelled() { + callback.cancelled(); + } + + }); + } else { + callback.completed(this); + } + } + + @Override + public void releaseConnection() { + final Endpoint endpoint = sessionRef.getAndSet(null); + if (endpoint != null && !reusable) { + endpoint.session.shutdown(ShutdownType.GRACEFUL); + } + } + + @Override + public void discardConnection() { + final Endpoint endpoint = sessionRef.getAndSet(null); + if (endpoint != null) { + endpoint.session.shutdown(ShutdownType.GRACEFUL); + } + } + + @Override + public boolean validateConnection() { + if (reusable) { + final Endpoint endpoint = sessionRef.get(); + return endpoint != null && !endpoint.session.isClosed(); + } else { + final Endpoint endpoint = sessionRef.getAndSet(null); + if (endpoint != null) { + endpoint.session.shutdown(ShutdownType.GRACEFUL); + } + } + return false; + } + + @Override + public boolean isConnected() { + final Endpoint endpoint = sessionRef.get(); + return endpoint != null && !endpoint.session.isClosed(); + } + + + Endpoint ensureValid() { + final Endpoint endpoint = sessionRef.get(); + if (endpoint == null) { + throw new IllegalStateException("I/O session not acquired / already released"); + } + return endpoint; + } + + @Override + public void connect( + final HttpClientContext context, + final FutureCallback callback) { + final Endpoint endpoint = ensureValid(); + if (!endpoint.session.isClosed()) { + callback.completed(this); + } else { + final HttpHost target = endpoint.target; + final RequestConfig requestConfig = context.getRequestConfig(); + connPool.getSession(target, requestConfig.getConnectTimeout(), new FutureCallback() { + + @Override + public void completed(final IOSession ioSession) { + sessionRef.set(new Endpoint(target, ioSession)); + reusable = true; + callback.completed(InternalHttp2AsyncExecRuntime.this); + } + + @Override + public void failed(final Exception ex) { + callback.failed(ex); + } + + @Override + public void cancelled() { + callback.cancelled(); + } + + }); + } + + } + + @Override + public void upgradeTls(final HttpClientContext context) { + throw new UnsupportedOperationException(); + } + + @Override + public void execute(final AsyncClientExchangeHandler exchangeHandler, final HttpClientContext context) { + final Endpoint endpoint = ensureValid(); + final IOSession session = endpoint.session; + if (!session.isClosed()) { + if (log.isDebugEnabled()) { + log.debug(ConnPoolSupport.getId(endpoint) + ": executing " + ConnPoolSupport.getId(exchangeHandler)); + } + session.addLast(new ExecutionCommand(exchangeHandler, context)); + } else { + final HttpHost target = endpoint.target; + final RequestConfig requestConfig = context.getRequestConfig(); + connPool.getSession(target, requestConfig.getConnectTimeout(), new FutureCallback() { + + @Override + public void completed(final IOSession ioSession) { + sessionRef.set(new Endpoint(target, ioSession)); + reusable = true; + if (log.isDebugEnabled()) { + log.debug(ConnPoolSupport.getId(endpoint) + ": executing " + ConnPoolSupport.getId(exchangeHandler)); + } + session.addLast(new ExecutionCommand(exchangeHandler, context)); + } + + @Override + public void failed(final Exception ex) { + exchangeHandler.failed(ex); + } + + @Override + public void cancelled() { + exchangeHandler.failed(new InterruptedIOException()); + } + + }); + } + + } + + @Override + public void markConnectionReusable(final Object newState, final TimeValue newValidDuration) { + throw new UnsupportedOperationException(); + } + + @Override + public void markConnectionNonReusable() { + reusable = false; + } + + static class Endpoint { + + final HttpHost target; + final IOSession session; + + Endpoint(final HttpHost target, final IOSession session) { + this.target = target; + this.session = session; + } + } + +} diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java index c8f7e8eef..8d23479e6 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java @@ -27,63 +27,33 @@ package org.apache.hc.client5.http.impl.async; import java.io.Closeable; -import java.io.IOException; import java.util.List; -import java.util.Set; -import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.client5.http.HttpRoute; -import org.apache.hc.client5.http.async.AsyncExecCallback; -import org.apache.hc.client5.http.async.AsyncExecChain; import org.apache.hc.client5.http.async.AsyncExecRuntime; -import org.apache.hc.client5.http.async.methods.SimpleRequestProducer; import org.apache.hc.client5.http.auth.AuthSchemeProvider; import org.apache.hc.client5.http.auth.CredentialsProvider; -import org.apache.hc.client5.http.config.Configurable; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.cookie.CookieSpecProvider; import org.apache.hc.client5.http.cookie.CookieStore; -import org.apache.hc.client5.http.impl.ExecSupport; -import org.apache.hc.client5.http.impl.RequestCopier; import org.apache.hc.client5.http.nio.AsyncClientConnectionManager; import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.client5.http.routing.HttpRoutePlanner; -import org.apache.hc.core5.concurrent.BasicFuture; -import org.apache.hc.core5.concurrent.FutureCallback; -import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; -import org.apache.hc.core5.http.HttpResponse; -import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.config.Lookup; -import org.apache.hc.core5.http.nio.AsyncDataConsumer; -import org.apache.hc.core5.http.nio.AsyncEntityProducer; -import org.apache.hc.core5.http.nio.AsyncRequestProducer; -import org.apache.hc.core5.http.nio.AsyncResponseConsumer; -import org.apache.hc.core5.http.nio.DataStreamChannel; -import org.apache.hc.core5.http.nio.RequestChannel; -import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.reactor.DefaultConnectingIOReactor; -class InternalHttpAsyncClient extends AbstractHttpAsyncClientBase { +class InternalHttpAsyncClient extends InternalAbstractHttpAsyncClient { private final AsyncClientConnectionManager connmgr; - private final AsyncExecChainElement execChain; private final HttpRoutePlanner routePlanner; private final HttpVersionPolicy versionPolicy; - private final Lookup cookieSpecRegistry; - private final Lookup authSchemeRegistry; - private final CookieStore cookieStore; - private final CredentialsProvider credentialsProvider; - private final RequestConfig defaultConfig; - private final List closeables; InternalHttpAsyncClient( final DefaultConnectingIOReactor ioReactor, @@ -99,227 +69,27 @@ class InternalHttpAsyncClient extends AbstractHttpAsyncClientBase { final CredentialsProvider credentialsProvider, final RequestConfig defaultConfig, final List closeables) { - super(ioReactor, pushConsumerRegistry, threadFactory); + super(ioReactor, pushConsumerRegistry, threadFactory, execChain, + cookieSpecRegistry, authSchemeRegistry, cookieStore, credentialsProvider, defaultConfig, closeables); this.connmgr = connmgr; - this.execChain = execChain; this.routePlanner = routePlanner; this.versionPolicy = versionPolicy; - this.cookieSpecRegistry = cookieSpecRegistry; - this.authSchemeRegistry = authSchemeRegistry; - this.cookieStore = cookieStore; - this.credentialsProvider = credentialsProvider; - this.defaultConfig = defaultConfig; - this.closeables = closeables; } @Override - public void close() { - super.close(); - if (closeables != null) { - for (final Closeable closeable: closeables) { - try { - closeable.close(); - } catch (final IOException ex) { - log.error(ex.getMessage(), ex); - } - } - } - } - - private void setupContext(final HttpClientContext context) { - if (context.getAttribute(HttpClientContext.AUTHSCHEME_REGISTRY) == null) { - context.setAttribute(HttpClientContext.AUTHSCHEME_REGISTRY, authSchemeRegistry); - } - if (context.getAttribute(HttpClientContext.COOKIESPEC_REGISTRY) == null) { - context.setAttribute(HttpClientContext.COOKIESPEC_REGISTRY, cookieSpecRegistry); - } - if (context.getAttribute(HttpClientContext.COOKIE_STORE) == null) { - context.setAttribute(HttpClientContext.COOKIE_STORE, cookieStore); - } - if (context.getAttribute(HttpClientContext.CREDS_PROVIDER) == null) { - context.setAttribute(HttpClientContext.CREDS_PROVIDER, credentialsProvider); - } - if (context.getAttribute(HttpClientContext.REQUEST_CONFIG) == null) { - context.setAttribute(HttpClientContext.REQUEST_CONFIG, defaultConfig); - } + AsyncExecRuntime crerateAsyncExecRuntime() { + return new InternalHttpAsyncExecRuntime(log, connmgr, getConnectionInitiator(), versionPolicy); } @Override - public Future execute( - final AsyncRequestProducer requestProducer, - final AsyncResponseConsumer responseConsumer, - final HttpContext context, - final FutureCallback callback) { - ensureRunning(); - final BasicFuture future = new BasicFuture<>(callback); - try { - final HttpClientContext clientContext = HttpClientContext.adapt(context); - requestProducer.sendRequest(new RequestChannel() { - - @Override - public void sendRequest( - final HttpRequest request, - final EntityDetails entityDetails) throws HttpException, IOException { - - RequestConfig requestConfig = null; - if (request instanceof Configurable) { - requestConfig = ((Configurable) request).getConfig(); - } - if (requestConfig != null) { - clientContext.setRequestConfig(requestConfig); - } - final HttpHost target = routePlanner.determineTargetHost(request, clientContext); - final HttpRoute route = routePlanner.determineRoute(target, clientContext); - final String exchangeId = String.format("ex-%08X", ExecSupport.getNextExecNumber()); - final AsyncExecRuntime execRuntime = new AsyncExecRuntimeImpl(log, connmgr, getConnectionInitiator(), versionPolicy); - if (log.isDebugEnabled()) { - log.debug(exchangeId + ": preparing request execution"); - } - - final ProtocolVersion protocolVersion = clientContext.getProtocolVersion(); - if (route.isTunnelled() && protocolVersion.greaterEquals(HttpVersion.HTTP_2_0)) { - throw new HttpException("HTTP/2 tunneling not supported"); - } - - setupContext(clientContext); - - final AsyncExecChain.Scope scope = new AsyncExecChain.Scope(exchangeId, route, request, clientContext, execRuntime); - final AtomicReference resultRef = new AtomicReference<>(null); - final AtomicBoolean outputTerminated = new AtomicBoolean(false); - execChain.execute( - RequestCopier.INSTANCE.copy(request), - entityDetails != null ? new AsyncEntityProducer() { - - @Override - public void releaseResources() { - requestProducer.releaseResources(); - } - - @Override - public void failed(final Exception cause) { - requestProducer.failed(cause); - } - - @Override - public boolean isRepeatable() { - //TODO: use AsyncRequestProducer#isRepeatable once available - return requestProducer instanceof SimpleRequestProducer; - } - - @Override - public long getContentLength() { - return entityDetails.getContentLength(); - } - - @Override - public String getContentType() { - return entityDetails.getContentType(); - } - - @Override - public String getContentEncoding() { - return entityDetails.getContentEncoding(); - } - - @Override - public boolean isChunked() { - return entityDetails.isChunked(); - } - - @Override - public Set getTrailerNames() { - return entityDetails.getTrailerNames(); - } - - @Override - public int available() { - return requestProducer.available(); - } - - @Override - public void produce(final DataStreamChannel channel) throws IOException { - if (outputTerminated.get()) { - channel.endStream(); - return; - } - requestProducer.produce(channel); - } - - } : null, - scope, - new AsyncExecCallback() { - - @Override - public AsyncDataConsumer handleResponse( - final HttpResponse response, - final EntityDetails entityDetails) throws HttpException, IOException { - if (response.getCode() >= HttpStatus.SC_CLIENT_ERROR) { - outputTerminated.set(true); - requestProducer.releaseResources(); - } - responseConsumer.consumeResponse(response, entityDetails, - //TODO: eliminate this callback after upgrade to HttpCore 5.0b2 - new FutureCallback() { - - @Override - public void completed(final T result) { - resultRef.set(result); - } - - @Override - public void failed(final Exception ex) { - future.failed(ex); - } - - @Override - public void cancelled() { - future.cancel(); - } - - }); - return responseConsumer; - } - - @Override - public void completed() { - if (log.isDebugEnabled()) { - log.debug(exchangeId + ": message exchange successfully completed"); - } - try { - execRuntime.releaseConnection(); - future.completed(resultRef.getAndSet(null)); - } finally { - responseConsumer.releaseResources(); - requestProducer.releaseResources(); - } - } - - @Override - public void failed(final Exception cause) { - if (log.isDebugEnabled()) { - log.debug(exchangeId + ": request failed: " + cause.getMessage()); - } - try { - execRuntime.discardConnection(); - responseConsumer.failed(cause); - } finally { - try { - future.failed(cause); - } finally { - responseConsumer.releaseResources(); - requestProducer.releaseResources(); - } - } - } - - }); - } - - }); - } catch (final HttpException | IOException ex) { - future.failed(ex); + HttpRoute determineRoute(final HttpRequest request, final HttpClientContext clientContext) throws HttpException { + final HttpHost target = routePlanner.determineTargetHost(request, clientContext); + final HttpRoute route = routePlanner.determineRoute(target, clientContext); + final ProtocolVersion protocolVersion = clientContext.getProtocolVersion(); + if (route.isTunnelled() && protocolVersion.greaterEquals(HttpVersion.HTTP_2_0)) { + throw new HttpException("HTTP/2 tunneling not supported"); } - return future; + return route; } } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncExecRuntimeImpl.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncExecRuntime.java similarity index 97% rename from httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncExecRuntimeImpl.java rename to httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncExecRuntime.java index 88d6bb3c5..23ed986de 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncExecRuntimeImpl.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncExecRuntime.java @@ -45,7 +45,7 @@ import org.apache.hc.core5.reactor.ConnectionInitiator; import org.apache.hc.core5.util.TimeValue; import org.apache.logging.log4j.Logger; -class AsyncExecRuntimeImpl implements AsyncExecRuntime { +class InternalHttpAsyncExecRuntime implements AsyncExecRuntime { private final Logger log; private final AsyncClientConnectionManager manager; @@ -56,7 +56,7 @@ class AsyncExecRuntimeImpl implements AsyncExecRuntime { private volatile Object state; private volatile TimeValue validDuration; - AsyncExecRuntimeImpl( + InternalHttpAsyncExecRuntime( final Logger log, final AsyncClientConnectionManager manager, final ConnectionInitiator connectionInitiator, @@ -90,7 +90,7 @@ class AsyncExecRuntimeImpl implements AsyncExecRuntime { public void completed(final AsyncConnectionEndpoint connectionEndpoint) { endpointRef.set(connectionEndpoint); reusable = connectionEndpoint.isConnected(); - callback.completed(AsyncExecRuntimeImpl.this); + callback.completed(InternalHttpAsyncExecRuntime.this); } @Override @@ -197,7 +197,7 @@ class AsyncExecRuntimeImpl implements AsyncExecRuntime { if (TimeValue.isPositive(socketTimeout)) { endpoint.setSocketTimeout(socketTimeout.toMillisIntBound()); } - callback.completed(AsyncExecRuntimeImpl.this); + callback.completed(InternalHttpAsyncExecRuntime.this); } @Override