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