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 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 }