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.Test;
71  import org.junit.jupiter.api.extension.RegisterExtension;
72  
73  /**
74   * Client protocol handling tests.
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 }