Skip to content

Commit 55cdd9e

Browse files
committed
Experimental connections pool implementation that acts as a caching facade in front of a standard ManagedConnPool and shares already leased connections to multiplex message exchanges over active HTTP/2 connections.
1 parent 90da166 commit 55cdd9e

File tree

15 files changed

+1165
-63
lines changed

15 files changed

+1165
-63
lines changed

httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/HttpIntegrationTests.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,4 +213,24 @@ public AuthenticationH2Tls() throws Exception {
213213

214214
}
215215

216+
@Nested
217+
@DisplayName("HTTP message multiplexing (HTTP/2)")
218+
class RequestMultiplexing extends TestHttpAsyncRequestMultiplexing {
219+
220+
public RequestMultiplexing() {
221+
super(URIScheme.HTTP);
222+
}
223+
224+
}
225+
226+
@Nested
227+
@DisplayName("HTTP message multiplexing (HTTP/2, TLS)")
228+
class RequestMultiplexingTls extends TestHttpAsyncRequestMultiplexing {
229+
230+
public RequestMultiplexingTls() {
231+
super(URIScheme.HTTPS);
232+
}
233+
234+
}
235+
216236
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* ====================================================================
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
* ====================================================================
20+
*
21+
* This software consists of voluntary contributions made by many
22+
* individuals on behalf of the Apache Software Foundation. For more
23+
* information on the Apache Software Foundation, please see
24+
* <http://www.apache.org/>.
25+
*
26+
*/
27+
package org.apache.hc.client5.testing.async;
28+
29+
import static org.hamcrest.MatcherAssert.assertThat;
30+
31+
import java.util.LinkedList;
32+
import java.util.Queue;
33+
import java.util.Random;
34+
import java.util.concurrent.Future;
35+
36+
import org.apache.hc.client5.http.config.TlsConfig;
37+
import org.apache.hc.client5.http.protocol.HttpClientContext;
38+
import org.apache.hc.client5.testing.extension.async.ClientProtocolLevel;
39+
import org.apache.hc.client5.testing.extension.async.ServerProtocolLevel;
40+
import org.apache.hc.client5.testing.extension.async.TestAsyncClient;
41+
import org.apache.hc.core5.http.ContentType;
42+
import org.apache.hc.core5.http.HttpHost;
43+
import org.apache.hc.core5.http.HttpResponse;
44+
import org.apache.hc.core5.http.Message;
45+
import org.apache.hc.core5.http.Method;
46+
import org.apache.hc.core5.http.URIScheme;
47+
import org.apache.hc.core5.http.nio.entity.AsyncEntityProducers;
48+
import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityConsumer;
49+
import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
50+
import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
51+
import org.apache.hc.core5.http2.HttpVersionPolicy;
52+
import org.hamcrest.CoreMatchers;
53+
import org.junit.jupiter.api.Test;
54+
55+
abstract class TestHttpAsyncRequestMultiplexing extends AbstractIntegrationTestBase {
56+
57+
public TestHttpAsyncRequestMultiplexing(final URIScheme uriScheme) {
58+
super(uriScheme, ClientProtocolLevel.MINIMAL, ServerProtocolLevel.H2_ONLY);
59+
}
60+
61+
@Test
62+
void testConcurrentPostRequests() throws Exception {
63+
configureServer(bootstrap -> bootstrap.register("/echo/*", AsyncEchoHandler::new));
64+
configureClient(custimizer -> custimizer
65+
.setDefaultTlsConfig(TlsConfig.custom()
66+
.setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2)
67+
.build())
68+
.useMessageMultiplexing()
69+
);
70+
final HttpHost target = startServer();
71+
final TestAsyncClient client = startClient();
72+
final byte[] b1 = new byte[1024];
73+
final Random rnd = new Random(System.currentTimeMillis());
74+
rnd.nextBytes(b1);
75+
76+
final int reqCount = 200;
77+
78+
final Queue<Future<Message<HttpResponse, byte[]>>> queue = new LinkedList<>();
79+
for (int i = 0; i < reqCount; i++) {
80+
final Future<Message<HttpResponse, byte[]>> future = client.execute(
81+
new BasicRequestProducer(Method.POST, target, "/echo/",
82+
AsyncEntityProducers.create(b1, ContentType.APPLICATION_OCTET_STREAM)),
83+
new BasicResponseConsumer<>(new BasicAsyncEntityConsumer()), HttpClientContext.create(), null);
84+
queue.add(future);
85+
}
86+
87+
while (!queue.isEmpty()) {
88+
final Future<Message<HttpResponse, byte[]>> future = queue.remove();
89+
final Message<HttpResponse, byte[]> responseMessage = future.get();
90+
assertThat(responseMessage, CoreMatchers.notNullValue());
91+
final HttpResponse response = responseMessage.getHead();
92+
assertThat(response.getCode(), CoreMatchers.equalTo(200));
93+
final byte[] b2 = responseMessage.getBody();
94+
assertThat(b1, CoreMatchers.equalTo(b2));
95+
}
96+
}
97+
98+
}

httpclient5-testing/src/test/java/org/apache/hc/client5/testing/compatibility/async/HttpAsyncClientCompatibilityTest.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
*/
2727
package org.apache.hc.client5.testing.compatibility.async;
2828

29+
import java.util.Queue;
30+
import java.util.concurrent.ConcurrentLinkedQueue;
31+
import java.util.concurrent.CountDownLatch;
2932
import java.util.concurrent.Future;
3033

3134
import org.apache.hc.client5.http.ContextBuilder;
@@ -38,10 +41,13 @@
3841
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
3942
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
4043
import org.apache.hc.client5.http.protocol.HttpClientContext;
44+
import org.apache.hc.client5.testing.Result;
4145
import org.apache.hc.client5.testing.extension.async.HttpAsyncClientResource;
46+
import org.apache.hc.core5.concurrent.FutureCallback;
4247
import org.apache.hc.core5.http.HttpHost;
4348
import org.apache.hc.core5.http.HttpStatus;
4449
import org.apache.hc.core5.http.HttpVersion;
50+
import org.apache.hc.core5.http.RequestNotExecutedException;
4551
import org.apache.hc.core5.http2.HttpVersionPolicy;
4652
import org.apache.hc.core5.util.Timeout;
4753
import org.junit.jupiter.api.Assertions;
@@ -51,6 +57,7 @@
5157
public abstract class HttpAsyncClientCompatibilityTest {
5258

5359
static final Timeout TIMEOUT = Timeout.ofSeconds(5);
60+
static final Timeout LONG_TIMEOUT = Timeout.ofSeconds(30);
5461

5562
private final HttpVersionPolicy versionPolicy;
5663
private final HttpHost target;
@@ -119,6 +126,54 @@ void test_sequential_gets() throws Exception {
119126
}
120127
}
121128

129+
@Test
130+
void test_concurrent_gets() throws Exception {
131+
final CloseableHttpAsyncClient client = client();
132+
133+
final String[] requestUris = new String[] {"/111", "/222", "/333"};
134+
final int n = 200;
135+
final Queue<Result<Void>> queue = new ConcurrentLinkedQueue<>();
136+
final CountDownLatch latch = new CountDownLatch(requestUris.length * n);
137+
138+
for (int i = 0; i < n; i++) {
139+
for (final String requestUri: requestUris) {
140+
final SimpleHttpRequest request = SimpleRequestBuilder.get()
141+
.setHttpHost(target)
142+
.setPath(requestUri)
143+
.build();
144+
final HttpClientContext context = context();
145+
client.execute(request, context, new FutureCallback<SimpleHttpResponse>() {
146+
147+
@Override
148+
public void completed(final SimpleHttpResponse response) {
149+
queue.add(new Result<>(request, response, null));
150+
latch.countDown();
151+
}
152+
153+
@Override
154+
public void failed(final Exception ex) {
155+
queue.add(new Result<>(request, ex));
156+
latch.countDown();
157+
}
158+
159+
@Override
160+
public void cancelled() {
161+
queue.add(new Result<>(request, new RequestNotExecutedException()));
162+
latch.countDown();
163+
}
164+
165+
});
166+
}
167+
}
168+
Assertions.assertTrue(latch.await(LONG_TIMEOUT.getDuration(), LONG_TIMEOUT.getTimeUnit()));
169+
Assertions.assertEquals(requestUris.length * n, queue.size());
170+
for (final Result<Void> result : queue) {
171+
if (result.isOK()) {
172+
Assertions.assertEquals(HttpStatus.SC_OK, result.response.getCode());
173+
}
174+
}
175+
}
176+
122177
@Test
123178
void test_auth_failure_wrong_auth_scope() throws Exception {
124179
addCredentials(

httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/H2OnlyMinimalTestClientBuilder.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ public TestAsyncClientBuilder setH2Config(final H2Config h2Config) {
6868
return this;
6969
}
7070

71+
@Override
72+
public TestAsyncClientBuilder useMessageMultiplexing() {
73+
return this;
74+
}
75+
7176
@Override
7277
public TestAsyncClient build() throws Exception {
7378
final CloseableHttpAsyncClient client = HttpAsyncClients.createHttp2Minimal(

httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/H2OnlyTestClientBuilder.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ public TestAsyncClientBuilder setTlsStrategy(final TlsStrategy tlsStrategy) {
9797
return this;
9898
}
9999

100+
@Override
101+
public TestAsyncClientBuilder useMessageMultiplexing() {
102+
return this;
103+
}
104+
100105
@Override
101106
public TestAsyncClientBuilder setH2Config(final H2Config h2Config) {
102107
this.h2Config = h2Config;

httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/HttpAsyncClientResource.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public HttpAsyncClientResource(final HttpVersionPolicy versionPolicy) throws IOE
6161
.setDefaultTlsConfig(TlsConfig.custom()
6262
.setVersionPolicy(versionPolicy)
6363
.build())
64+
.setMessageMultiplexing(true)
6465
.build());
6566
} catch (final CertificateException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException ex) {
6667
throw new IllegalStateException(ex);

httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/MinimalTestClientBuilder.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ public TestAsyncClientBuilder setDefaultTlsConfig(final TlsConfig tlsConfig) {
7777
return this;
7878
}
7979

80+
@Override
81+
public TestAsyncClientBuilder useMessageMultiplexing() {
82+
this.connectionManagerBuilder.setMessageMultiplexing(true);
83+
return this;
84+
}
85+
8086
@Override
8187
public TestAsyncClientBuilder setHttp1Config(final Http1Config http1Config) {
8288
this.http1Config = http1Config;

httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/StandardTestClientBuilder.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ public TestAsyncClientBuilder setDefaultTlsConfig(final TlsConfig tlsConfig) {
111111
return this;
112112
}
113113

114+
@Override
115+
public TestAsyncClientBuilder useMessageMultiplexing() {
116+
this.connectionManagerBuilder.setMessageMultiplexing(true);
117+
return this;
118+
}
119+
114120
@Override
115121
public TestAsyncClientBuilder setHttp1Config(final Http1Config http1Config) {
116122
this.clientBuilder.setHttp1Config(http1Config);

httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/TestAsyncClientBuilder.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ default TestAsyncClientBuilder setDefaultTlsConfig(TlsConfig tlsConfig) {
7373
throw new UnsupportedOperationException("Operation not supported by " + getProtocolLevel());
7474
}
7575

76+
default TestAsyncClientBuilder useMessageMultiplexing() {
77+
throw new UnsupportedOperationException("Operation not supported by " + getProtocolLevel());
78+
}
79+
7680
default TestAsyncClientBuilder setHttp1Config(Http1Config http1Config) {
7781
throw new UnsupportedOperationException("Operation not supported by " + getProtocolLevel());
7882
}

0 commit comments

Comments
 (0)