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