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 java.io.ByteArrayInputStream;
30 import java.io.IOException;
31 import java.net.URI;
32 import java.util.Random;
33 import java.util.concurrent.Executors;
34 import java.util.concurrent.ScheduledExecutorService;
35 import java.util.concurrent.TimeUnit;
36 import java.util.function.Consumer;
37
38 import org.apache.hc.client5.http.HttpRequestRetryStrategy;
39 import org.apache.hc.client5.http.classic.methods.HttpGet;
40 import org.apache.hc.client5.http.classic.methods.HttpPost;
41 import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
42 import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
43 import org.apache.hc.client5.http.protocol.HttpClientContext;
44 import org.apache.hc.client5.http.protocol.RedirectLocations;
45 import org.apache.hc.client5.http.utils.URIUtils;
46 import org.apache.hc.client5.testing.sync.extension.TestClientResources;
47 import org.apache.hc.core5.http.ClassicHttpRequest;
48 import org.apache.hc.core5.http.ClassicHttpResponse;
49 import org.apache.hc.core5.http.Header;
50 import org.apache.hc.core5.http.HttpException;
51 import org.apache.hc.core5.http.HttpHost;
52 import org.apache.hc.core5.http.HttpRequest;
53 import org.apache.hc.core5.http.HttpRequestInterceptor;
54 import org.apache.hc.core5.http.HttpResponse;
55 import org.apache.hc.core5.http.HttpStatus;
56 import org.apache.hc.core5.http.URIScheme;
57 import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
58 import org.apache.hc.core5.http.io.HttpClientConnection;
59 import org.apache.hc.core5.http.io.HttpRequestHandler;
60 import org.apache.hc.core5.http.io.HttpResponseInformationCallback;
61 import org.apache.hc.core5.http.io.entity.EntityUtils;
62 import org.apache.hc.core5.http.io.entity.InputStreamEntity;
63 import org.apache.hc.core5.http.io.entity.StringEntity;
64 import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
65 import org.apache.hc.core5.http.protocol.HttpContext;
66 import org.apache.hc.core5.net.URIBuilder;
67 import org.apache.hc.core5.testing.classic.ClassicTestServer;
68 import org.apache.hc.core5.util.TimeValue;
69 import org.apache.hc.core5.util.Timeout;
70 import org.junit.jupiter.api.Assertions;
71 import org.junit.jupiter.api.Disabled;
72 import org.junit.jupiter.api.Test;
73 import org.junit.jupiter.api.extension.RegisterExtension;
74
75
76
77
78 public abstract class TestClientRequestExecution {
79
80 public static final Timeout TIMEOUT = Timeout.ofMinutes(1);
81
82 @RegisterExtension
83 private TestClientResources testResources;
84
85 protected TestClientRequestExecution(final URIScheme scheme) {
86 this.testResources = new TestClientResources(scheme, TIMEOUT);
87 }
88
89 public URIScheme scheme() {
90 return testResources.scheme();
91 }
92
93 public ClassicTestServer startServer() throws IOException {
94 return testResources.startServer(null, null, null);
95 }
96
97 public CloseableHttpClient startClient(final Consumer<HttpClientBuilder> clientCustomizer) throws Exception {
98 return testResources.startClient(clientCustomizer);
99 }
100
101 public CloseableHttpClient startClient() throws Exception {
102 return testResources.startClient(builder -> {});
103 }
104
105 public HttpHost targetHost() {
106 return testResources.targetHost();
107 }
108
109 private static class SimpleService implements HttpRequestHandler {
110
111 public SimpleService() {
112 super();
113 }
114
115 @Override
116 public void handle(
117 final ClassicHttpRequest request,
118 final ClassicHttpResponse response,
119 final HttpContext context) throws HttpException, IOException {
120 response.setCode(HttpStatus.SC_OK);
121 final StringEntity entity = new StringEntity("Whatever");
122 response.setEntity(entity);
123 }
124 }
125
126 private static class FaultyHttpRequestExecutor extends HttpRequestExecutor {
127
128 private static final String MARKER = "marker";
129
130 private final String failureMsg;
131
132 public FaultyHttpRequestExecutor(final String failureMsg) {
133 this.failureMsg = failureMsg;
134 }
135
136 @Override
137 public ClassicHttpResponse execute(
138 final ClassicHttpRequest request,
139 final HttpClientConnection conn,
140 final HttpContext context) throws IOException, HttpException {
141 return execute(request, conn, null, context);
142 }
143
144 @Override
145 public ClassicHttpResponse execute(
146 final ClassicHttpRequest request,
147 final HttpClientConnection conn,
148 final HttpResponseInformationCallback informationCallback,
149 final HttpContext context) throws IOException, HttpException {
150
151 final ClassicHttpResponse response = super.execute(request, conn, informationCallback, context);
152 final Object marker = context.getAttribute(MARKER);
153 if (marker == null) {
154 context.setAttribute(MARKER, Boolean.TRUE);
155 throw new IOException(failureMsg);
156 }
157 return response;
158 }
159
160 }
161
162 @Test
163 public void testAutoGeneratedHeaders() throws Exception {
164 final ClassicTestServer server = startServer();
165 server.registerHandler("*", new SimpleService());
166 final HttpHost target = targetHost();
167
168 final HttpRequestInterceptor interceptor = (request, entityDetails, context) -> request.addHeader("my-header", "stuff");
169
170 final HttpRequestRetryStrategy requestRetryStrategy = new HttpRequestRetryStrategy() {
171
172 @Override
173 public boolean retryRequest(
174 final HttpRequest request,
175 final IOException exception,
176 final int executionCount,
177 final HttpContext context) {
178 return true;
179 }
180
181 @Override
182 public boolean retryRequest(
183 final HttpResponse response,
184 final int executionCount,
185 final HttpContext context) {
186 return false;
187 }
188
189 @Override
190 public TimeValue getRetryInterval(
191 final HttpResponse response,
192 final int executionCount,
193 final HttpContext context) {
194 return TimeValue.ofSeconds(1L);
195 }
196
197 };
198
199 final CloseableHttpClient client = startClient(builder -> builder
200 .addRequestInterceptorFirst(interceptor)
201 .setRequestExecutor(new FaultyHttpRequestExecutor("Oppsie"))
202 .setRetryStrategy(requestRetryStrategy)
203 );
204
205 final HttpClientContext context = HttpClientContext.create();
206
207 final HttpGet httpget = new HttpGet("/");
208
209 client.execute(target, httpget, context, response -> {
210 EntityUtils.consume(response.getEntity());
211 final HttpRequest reqWrapper = context.getRequest();
212
213 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
214
215 final Header[] myheaders = reqWrapper.getHeaders("my-header");
216 Assertions.assertNotNull(myheaders);
217 Assertions.assertEquals(1, myheaders.length);
218 return null;
219 });
220 }
221
222 @Test
223 public void testNonRepeatableEntity() throws Exception {
224 final ClassicTestServer server = startServer();
225 server.registerHandler("*", new SimpleService());
226 final HttpHost target = targetHost();
227
228 final HttpRequestRetryStrategy requestRetryStrategy = new HttpRequestRetryStrategy() {
229
230 @Override
231 public boolean retryRequest(
232 final HttpRequest request,
233 final IOException exception,
234 final int executionCount,
235 final HttpContext context) {
236 return true;
237 }
238
239 @Override
240 public boolean retryRequest(
241 final HttpResponse response,
242 final int executionCount,
243 final HttpContext context) {
244 return false;
245 }
246
247 @Override
248 public TimeValue getRetryInterval(
249 final HttpResponse response,
250 final int executionCount,
251 final HttpContext context) {
252 return TimeValue.ofSeconds(1L);
253 }
254
255 };
256
257 final CloseableHttpClient client = startClient(builder -> builder
258 .setRequestExecutor(new FaultyHttpRequestExecutor("a message showing that this failed"))
259 .setRetryStrategy(requestRetryStrategy)
260 );
261
262 final HttpClientContext context = HttpClientContext.create();
263
264 final HttpPost httppost = new HttpPost("/");
265 httppost.setEntity(new InputStreamEntity(
266 new ByteArrayInputStream(
267 new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 } ),
268 -1, null));
269 Assertions.assertThrows(IOException.class, () ->
270 client.execute(target, httppost, context, response -> null));
271 }
272
273 @Test
274 public void testNonCompliantURI() throws Exception {
275 final ClassicTestServer server = startServer();
276 server.registerHandler("*", new SimpleService());
277 final HttpHost target = targetHost();
278
279 final CloseableHttpClient client = startClient();
280
281 final HttpClientContext context = HttpClientContext.create();
282 final ClassicHttpRequest request = new BasicClassicHttpRequest("GET", "{{|boom|}}");
283 client.execute(target, request, context, response -> {
284 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
285 EntityUtils.consume(response.getEntity());
286 return null;
287 });
288
289 final HttpRequest reqWrapper = context.getRequest();
290
291 Assertions.assertEquals("{{|boom|}}", reqWrapper.getRequestUri());
292 }
293
294 @Test
295 public void testRelativeRequestURIWithFragment() throws Exception {
296 final ClassicTestServer server = startServer();
297 server.registerHandler("*", new SimpleService());
298 final HttpHost target = targetHost();
299
300 final CloseableHttpClient client = startClient();
301
302 final HttpGet httpget = new HttpGet("/stuff#blahblah");
303 final HttpClientContext context = HttpClientContext.create();
304
305 client.execute(target, httpget, context, response -> {
306 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
307 EntityUtils.consume(response.getEntity());
308 return null;
309 });
310
311 final HttpRequest request = context.getRequest();
312 Assertions.assertEquals("/stuff", request.getRequestUri());
313 }
314
315 @Test
316 public void testAbsoluteRequestURIWithFragment() throws Exception {
317 final ClassicTestServer server = startServer();
318 server.registerHandler("*", new SimpleService());
319 final HttpHost target = targetHost();
320
321 final CloseableHttpClient client = startClient();
322
323 final URI uri = new URIBuilder()
324 .setHost(target.getHostName())
325 .setPort(target.getPort())
326 .setScheme(target.getSchemeName())
327 .setPath("/stuff")
328 .setFragment("blahblah")
329 .build();
330
331 final HttpGet httpget = new HttpGet(uri);
332 final HttpClientContext context = HttpClientContext.create();
333
334 client.execute(httpget, context, response -> {
335 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
336 return null;
337 });
338
339 final HttpRequest request = context.getRequest();
340 Assertions.assertEquals("/stuff", request.getRequestUri());
341
342 final RedirectLocations redirectLocations = context.getRedirectLocations();
343 final URI location = URIUtils.resolve(uri, target, redirectLocations.getAll());
344 Assertions.assertEquals(uri, location);
345 }
346
347 @Test @Disabled("Fails intermittently with GitHub Actions")
348 public void testRequestCancellation() throws Exception {
349 startServer();
350 final HttpHost target = targetHost();
351
352 final CloseableHttpClient client = testResources.startClient(
353 builder -> builder
354 .setMaxConnPerRoute(1)
355 .setMaxConnTotal(1),
356 builder -> {});
357
358 final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
359 try {
360
361 for (int i = 0; i < 20; i++) {
362 final HttpGet httpget = new HttpGet("/random/1000");
363
364 executorService.schedule(httpget::cancel, 1, TimeUnit.MILLISECONDS);
365
366 try {
367 client.execute(target, httpget, response -> {
368 EntityUtils.consume(response.getEntity());
369 return null;
370 });
371
372 } catch (final Exception ignore) {
373 }
374 }
375
376 final Random rnd = new Random();
377 for (int i = 0; i < 20; i++) {
378 final HttpGet httpget = new HttpGet("/random/1000");
379
380 executorService.schedule(httpget::cancel, rnd.nextInt(200), TimeUnit.MILLISECONDS);
381
382 try {
383 client.execute(target, httpget, response -> {
384 EntityUtils.consume(response.getEntity());
385 return null;
386 });
387 } catch (final Exception ignore) {
388 }
389
390 }
391
392 for (int i = 0; i < 5; i++) {
393 final HttpGet httpget = new HttpGet("/random/1000");
394 client.execute(target, httpget, response -> {
395 EntityUtils.consume(response.getEntity());
396 return null;
397 });
398 }
399
400 } finally {
401 executorService.shutdownNow();
402 }
403 }
404
405 }