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