View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
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   * Client protocol handling tests.
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 }