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 static org.hamcrest.MatcherAssert.assertThat;
30  
31  import java.io.IOException;
32  import java.util.LinkedList;
33  import java.util.Queue;
34  import java.util.concurrent.CancellationException;
35  import java.util.concurrent.CountDownLatch;
36  import java.util.concurrent.ExecutionException;
37  import java.util.concurrent.ExecutorService;
38  import java.util.concurrent.Executors;
39  import java.util.concurrent.Future;
40  import java.util.concurrent.FutureTask;
41  import java.util.concurrent.TimeUnit;
42  import java.util.concurrent.TimeoutException;
43  import java.util.concurrent.atomic.AtomicBoolean;
44  
45  import org.apache.hc.client5.http.classic.methods.HttpGet;
46  import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
47  import org.apache.hc.client5.http.impl.classic.FutureRequestExecutionService;
48  import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
49  import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
50  import org.apache.hc.client5.http.io.HttpClientConnectionManager;
51  import org.apache.hc.client5.http.protocol.HttpClientContext;
52  import org.apache.hc.core5.concurrent.FutureCallback;
53  import org.apache.hc.core5.http.ClassicHttpResponse;
54  import org.apache.hc.core5.http.impl.bootstrap.HttpServer;
55  import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap;
56  import org.apache.hc.core5.http.io.HttpClientResponseHandler;
57  import org.hamcrest.CoreMatchers;
58  import org.junit.jupiter.api.AfterEach;
59  import org.junit.jupiter.api.Assertions;
60  import org.junit.jupiter.api.BeforeEach;
61  import org.junit.jupiter.api.Test;
62  
63  @SuppressWarnings("boxing") // test code
64  public class TestFutureRequestExecutionService {
65  
66      private HttpServer localServer;
67      private String uri;
68      private FutureRequestExecutionService httpAsyncClientWithFuture;
69  
70      private final AtomicBoolean blocked = new AtomicBoolean(false);
71  
72      @BeforeEach
73      public void before() throws Exception {
74          this.localServer = ServerBootstrap.bootstrap()
75                  .setCanonicalHostName("localhost")
76                  .register("/wait", (request, response, context) -> {
77                      try {
78                          while(blocked.get()) {
79                              Thread.sleep(10);
80                          }
81                      } catch (final InterruptedException e) {
82                          throw new IllegalStateException(e);
83                      }
84                      response.setCode(200);
85                  }).create();
86  
87          this.localServer.start();
88          uri = "http://localhost:" + this.localServer.getLocalPort() + "/wait";
89          final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create()
90                  .setMaxConnPerRoute(5)
91                  .build();
92          final CloseableHttpClient httpClient = HttpClientBuilder.create()
93                  .setConnectionManager(cm)
94                  .build();
95          final ExecutorService executorService = Executors.newFixedThreadPool(5);
96          httpAsyncClientWithFuture = new FutureRequestExecutionService(httpClient, executorService);
97      }
98  
99      @AfterEach
100     public void after() throws Exception {
101         blocked.set(false); // any remaining requests should unblock
102         this.localServer.stop();
103         httpAsyncClientWithFuture.close();
104     }
105 
106     @Test
107     public void shouldExecuteSingleCall() throws InterruptedException, ExecutionException {
108         final FutureTask<Boolean> task = httpAsyncClientWithFuture.execute(
109             new HttpGet(uri), HttpClientContext.create(), new OkidokiHandler());
110         Assertions.assertTrue(task.get(), "request should have returned OK");
111     }
112 
113     @Test
114     public void shouldCancel() throws InterruptedException, ExecutionException {
115         final FutureTask<Boolean> task = httpAsyncClientWithFuture.execute(
116             new HttpGet(uri), HttpClientContext.create(), new OkidokiHandler());
117         task.cancel(true);
118         final Exception exception = Assertions.assertThrows(Exception.class, task::get);
119         assertThat(exception, CoreMatchers.anyOf(
120                 CoreMatchers.instanceOf(CancellationException.class),
121                 CoreMatchers.instanceOf(ExecutionException.class)));
122     }
123 
124     @Test
125     public void shouldTimeout() throws InterruptedException, ExecutionException, TimeoutException {
126         blocked.set(true);
127         final FutureTask<Boolean> task = httpAsyncClientWithFuture.execute(
128             new HttpGet(uri), HttpClientContext.create(), new OkidokiHandler());
129         Assertions.assertThrows(TimeoutException.class, () ->
130                 task.get(10, TimeUnit.MILLISECONDS));
131     }
132 
133     @Test
134     public void shouldExecuteMultipleCalls() throws Exception {
135         final int reqNo = 100;
136         final Queue<Future<Boolean>> tasks = new LinkedList<>();
137         for(int i = 0; i < reqNo; i++) {
138             final Future<Boolean> task = httpAsyncClientWithFuture.execute(
139                     new HttpGet(uri), HttpClientContext.create(), new OkidokiHandler());
140             tasks.add(task);
141         }
142         for (final Future<Boolean> task : tasks) {
143             final Boolean b = task.get();
144             Assertions.assertNotNull(b);
145             Assertions.assertTrue(b, "request should have returned OK");
146         }
147     }
148 
149     @Test
150     public void shouldExecuteMultipleCallsAndCallback() throws Exception {
151         final int reqNo = 100;
152         final Queue<Future<Boolean>> tasks = new LinkedList<>();
153         final CountDownLatch latch = new CountDownLatch(reqNo);
154         for(int i = 0; i < reqNo; i++) {
155             final Future<Boolean> task = httpAsyncClientWithFuture.execute(
156                     new HttpGet(uri), HttpClientContext.create(),
157                     new OkidokiHandler(), new CountingCallback(latch));
158             tasks.add(task);
159         }
160         Assertions.assertTrue(latch.await(5, TimeUnit.SECONDS));
161         for (final Future<Boolean> task : tasks) {
162             final Boolean b = task.get();
163             Assertions.assertNotNull(b);
164             Assertions.assertTrue(b, "request should have returned OK");
165         }
166     }
167 
168     private final class CountingCallback implements FutureCallback<Boolean> {
169 
170         private final CountDownLatch latch;
171 
172         CountingCallback(final CountDownLatch latch) {
173             super();
174             this.latch = latch;
175         }
176 
177         @Override
178         public void failed(final Exception ex) {
179             latch.countDown();
180         }
181 
182         @Override
183         public void completed(final Boolean result) {
184             latch.countDown();
185         }
186 
187         @Override
188         public void cancelled() {
189             latch.countDown();
190         }
191     }
192 
193 
194     private final class OkidokiHandler implements HttpClientResponseHandler<Boolean> {
195         @Override
196         public Boolean handleResponse(
197                 final ClassicHttpResponse response) throws IOException {
198             return response.getCode() == 200;
199         }
200     }
201 
202 }