1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.apache.hc.client5.testing.sync;
28
29 import static org.hamcrest.MatcherAssert.assertThat;
30
31 import java.util.LinkedList;
32 import java.util.Queue;
33 import java.util.concurrent.CancellationException;
34 import java.util.concurrent.CountDownLatch;
35 import java.util.concurrent.ExecutionException;
36 import java.util.concurrent.ExecutorService;
37 import java.util.concurrent.Executors;
38 import java.util.concurrent.Future;
39 import java.util.concurrent.FutureTask;
40 import java.util.concurrent.TimeUnit;
41 import java.util.concurrent.TimeoutException;
42 import java.util.concurrent.atomic.AtomicBoolean;
43
44 import org.apache.hc.client5.http.classic.methods.HttpGet;
45 import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
46 import org.apache.hc.client5.http.impl.classic.FutureRequestExecutionService;
47 import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
48 import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
49 import org.apache.hc.client5.http.io.HttpClientConnectionManager;
50 import org.apache.hc.client5.http.protocol.HttpClientContext;
51 import org.apache.hc.core5.concurrent.FutureCallback;
52 import org.apache.hc.core5.http.ClassicHttpResponse;
53 import org.apache.hc.core5.http.impl.bootstrap.HttpServer;
54 import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap;
55 import org.apache.hc.core5.http.io.HttpClientResponseHandler;
56 import org.hamcrest.CoreMatchers;
57 import org.junit.jupiter.api.AfterEach;
58 import org.junit.jupiter.api.Assertions;
59 import org.junit.jupiter.api.BeforeEach;
60 import org.junit.jupiter.api.Disabled;
61 import org.junit.jupiter.api.Test;
62
63 @SuppressWarnings("boxing")
64 class TestFutureRequestExecutionService {
65
66 private HttpServer localServer;
67 private String uri;
68 private FutureRequestExecutionService httpAsyncClientWithFuture;
69
70 private final AtomicBoolean blocked = new AtomicBoolean(false);
71
72 @BeforeEach
73 void before() throws Exception {
74 this.localServer = ServerBootstrap.bootstrap()
75 .setCanonicalHostName("localhost")
76 .register("/wait", (request, response, context) -> {
77 try {
78 while(blocked.get()) {
79 Thread.sleep(10);
80 }
81 } catch (final InterruptedException e) {
82 throw new IllegalStateException(e);
83 }
84 response.setCode(200);
85 }).create();
86
87 this.localServer.start();
88 uri = "http://localhost:" + this.localServer.getLocalPort() + "/wait";
89 final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create()
90 .setMaxConnPerRoute(5)
91 .build();
92 final CloseableHttpClient httpClient = HttpClientBuilder.create()
93 .setConnectionManager(cm)
94 .build();
95 final ExecutorService executorService = Executors.newFixedThreadPool(5);
96 httpAsyncClientWithFuture = new FutureRequestExecutionService(httpClient, executorService);
97 }
98
99 @AfterEach
100 void after() throws Exception {
101 blocked.set(false);
102 this.localServer.stop();
103 httpAsyncClientWithFuture.close();
104 }
105
106 @Test
107 void shouldExecuteSingleCall() throws InterruptedException, ExecutionException {
108 final FutureTask<Boolean> task = httpAsyncClientWithFuture.execute(
109 new HttpGet(uri), HttpClientContext.create(), new OkidokiHandler());
110 Assertions.assertTrue(task.get(), "request should have returned OK");
111 }
112
113 @Test @Disabled("Fails intermittently with GitHub Actions. Needs to be revised")
114 void shouldCancel() {
115 final FutureTask<Boolean> task = httpAsyncClientWithFuture.execute(
116 new HttpGet(uri), HttpClientContext.create(), new OkidokiHandler());
117 task.cancel(true);
118 final Exception exception = Assertions.assertThrows(Exception.class, task::get);
119 assertThat(exception, CoreMatchers.anyOf(
120 CoreMatchers.instanceOf(CancellationException.class),
121 CoreMatchers.instanceOf(ExecutionException.class)));
122 }
123
124 @Test
125 void shouldTimeout() {
126 blocked.set(true);
127 final FutureTask<Boolean> task = httpAsyncClientWithFuture.execute(
128 new HttpGet(uri), HttpClientContext.create(), new OkidokiHandler());
129 Assertions.assertThrows(TimeoutException.class, () ->
130 task.get(10, TimeUnit.MILLISECONDS));
131 }
132
133 @Test
134 void shouldExecuteMultipleCalls() throws Exception {
135 final int reqNo = 100;
136 final Queue<Future<Boolean>> tasks = new LinkedList<>();
137 for(int i = 0; i < reqNo; i++) {
138 final Future<Boolean> task = httpAsyncClientWithFuture.execute(
139 new HttpGet(uri), HttpClientContext.create(), new OkidokiHandler());
140 tasks.add(task);
141 }
142 for (final Future<Boolean> task : tasks) {
143 final Boolean b = task.get();
144 Assertions.assertNotNull(b);
145 Assertions.assertTrue(b, "request should have returned OK");
146 }
147 }
148
149 @Test
150 void shouldExecuteMultipleCallsAndCallback() throws Exception {
151 final int reqNo = 100;
152 final Queue<Future<Boolean>> tasks = new LinkedList<>();
153 final CountDownLatch latch = new CountDownLatch(reqNo);
154 for(int i = 0; i < reqNo; i++) {
155 final Future<Boolean> task = httpAsyncClientWithFuture.execute(
156 new HttpGet(uri), HttpClientContext.create(),
157 new OkidokiHandler(), new CountingCallback(latch));
158 tasks.add(task);
159 }
160 Assertions.assertTrue(latch.await(5, TimeUnit.SECONDS));
161 for (final Future<Boolean> task : tasks) {
162 final Boolean b = task.get();
163 Assertions.assertNotNull(b);
164 Assertions.assertTrue(b, "request should have returned OK");
165 }
166 }
167
168 private static final class CountingCallback implements FutureCallback<Boolean> {
169
170 private final CountDownLatch latch;
171
172 CountingCallback(final CountDownLatch latch) {
173 super();
174 this.latch = latch;
175 }
176
177 @Override
178 public void failed(final Exception ex) {
179 latch.countDown();
180 }
181
182 @Override
183 public void completed(final Boolean result) {
184 latch.countDown();
185 }
186
187 @Override
188 public void cancelled() {
189 latch.countDown();
190 }
191 }
192
193
194 private static final class OkidokiHandler implements HttpClientResponseHandler<Boolean> {
195 @Override
196 public Boolean handleResponse(
197 final ClassicHttpResponse response) {
198 return response.getCode() == 200;
199 }
200 }
201
202 }