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