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  
28  package org.apache.hc.client5.testing.sync;
29  
30  import java.io.IOException;
31  import java.util.concurrent.TimeoutException;
32  
33  import org.apache.hc.client5.http.HttpRoute;
34  import org.apache.hc.client5.http.config.ConnectionConfig;
35  import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
36  import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
37  import org.apache.hc.client5.http.io.ConnectionEndpoint;
38  import org.apache.hc.client5.http.io.LeaseRequest;
39  import org.apache.hc.client5.http.protocol.HttpClientContext;
40  import org.apache.hc.client5.testing.classic.RandomHandler;
41  import org.apache.hc.client5.testing.sync.extension.TestClientResources;
42  import org.apache.hc.core5.http.ClassicHttpRequest;
43  import org.apache.hc.core5.http.ClassicHttpResponse;
44  import org.apache.hc.core5.http.HttpException;
45  import org.apache.hc.core5.http.HttpHost;
46  import org.apache.hc.core5.http.HttpStatus;
47  import org.apache.hc.core5.http.URIScheme;
48  import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
49  import org.apache.hc.core5.http.io.HttpClientConnection;
50  import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
51  import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
52  import org.apache.hc.core5.http.protocol.HttpContext;
53  import org.apache.hc.core5.http.protocol.HttpProcessor;
54  import org.apache.hc.core5.http.protocol.RequestConnControl;
55  import org.apache.hc.core5.http.protocol.RequestContent;
56  import org.apache.hc.core5.http.protocol.RequestTargetHost;
57  import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
58  import org.apache.hc.core5.pool.PoolReusePolicy;
59  import org.apache.hc.core5.testing.classic.ClassicTestServer;
60  import org.apache.hc.core5.util.TimeValue;
61  import org.apache.hc.core5.util.Timeout;
62  import org.junit.jupiter.api.Assertions;
63  import org.junit.jupiter.api.BeforeEach;
64  import org.junit.jupiter.api.Test;
65  import org.junit.jupiter.api.extension.RegisterExtension;
66  
67  /**
68   * Tests for {@code PoolingHttpClientConnectionManager} that do require a server
69   * to communicate with.
70   */
71  public class TestConnectionManagement {
72  
73      public static final Timeout TIMEOUT = Timeout.ofMinutes(1);
74  
75      @RegisterExtension
76      private TestClientResources testResources = new TestClientResources(URIScheme.HTTP, TIMEOUT);
77  
78      ConnectionEndpoint.RequestExecutor exec;
79  
80      @BeforeEach
81      public void setup() {
82          exec = new ConnectionEndpoint.RequestExecutor() {
83  
84              final HttpRequestExecutor requestExecutor = new HttpRequestExecutor();
85              final HttpProcessor httpProcessor = new DefaultHttpProcessor(
86                      new RequestTargetHost(), new RequestContent(), new RequestConnControl());
87              @Override
88              public ClassicHttpResponse execute(final ClassicHttpRequest request,
89                                                 final HttpClientConnection conn,
90                                                 final HttpContext context) throws IOException, HttpException {
91                  requestExecutor.preProcess(request, httpProcessor, context);
92                  final ClassicHttpResponse response = requestExecutor.execute(request, conn, context);
93                  requestExecutor.postProcess(response, httpProcessor, context);
94                  return response;
95              }
96  
97          };
98      }
99  
100     public ClassicTestServer startServer() throws IOException {
101         return testResources.startServer(null, null, null);
102     }
103 
104     public CloseableHttpClient startClient() throws Exception {
105         return testResources.startClient(b -> {}, b -> {});
106     }
107 
108     public HttpHost targetHost() {
109         return testResources.targetHost();
110     }
111 
112     /**
113      * Tests releasing and re-using a connection after a response is read.
114      */
115     @Test
116     public void testReleaseConnection() throws Exception {
117         final ClassicTestServer server = startServer();
118         server.registerHandler("/random/*", new RandomHandler());
119         final HttpHost target = targetHost();
120 
121         startClient();
122 
123         final PoolingHttpClientConnectionManager connManager = testResources.connManager();
124         connManager.setMaxTotal(1);
125 
126         final HttpRoute route = new HttpRoute(target, null, false);
127         final int rsplen = 8;
128         final String uri = "/random/" + rsplen;
129 
130         final ClassicHttpRequest request = new BasicClassicHttpRequest("GET", target, uri);
131         final HttpClientContext context = HttpClientContext.create();
132 
133         final LeaseRequest leaseRequest1 = connManager.lease("id1", route, null);
134         final ConnectionEndpoint endpoint1 = leaseRequest1.get(Timeout.ZERO_MILLISECONDS);
135 
136         connManager.connect(endpoint1, null, context);
137 
138         try (final ClassicHttpResponse response1 = endpoint1.execute("id1", request, exec, context)) {
139             Assertions.assertEquals(HttpStatus.SC_OK, response1.getCode());
140         }
141 
142         // check that there is no auto-release by default
143         // this should fail quickly, connection has not been released
144         final LeaseRequest leaseRequest2 = connManager.lease("id2", route, null);
145         Assertions.assertThrows(TimeoutException.class, () -> leaseRequest2.get(Timeout.ofMilliseconds(10)));
146 
147         endpoint1.close();
148         connManager.release(endpoint1, null, TimeValue.NEG_ONE_MILLISECOND);
149         final LeaseRequest leaseRequest3 = connManager.lease("id2", route, null);
150         final ConnectionEndpoint endpoint2 = leaseRequest3.get(Timeout.ZERO_MILLISECONDS);
151         Assertions.assertFalse(endpoint2.isConnected());
152 
153         connManager.connect(endpoint2, null, context);
154 
155         try (final ClassicHttpResponse response2 = endpoint2.execute("id2", request, exec, context)) {
156             Assertions.assertEquals(HttpStatus.SC_OK, response2.getCode());
157         }
158 
159         // release connection after marking it for re-use
160         // expect the next connection obtained to be open
161         connManager.release(endpoint2, null, TimeValue.NEG_ONE_MILLISECOND);
162 
163         final LeaseRequest leaseRequest4 = connManager.lease("id3", route, null);
164         final ConnectionEndpoint endpoint3 = leaseRequest4.get(Timeout.ZERO_MILLISECONDS);
165         Assertions.assertTrue(endpoint3.isConnected());
166 
167         // repeat the communication, no need to prepare the request again
168         try (final ClassicHttpResponse response3 = endpoint3.execute("id3", request, exec, context)) {
169             Assertions.assertEquals(HttpStatus.SC_OK, response3.getCode());
170         }
171 
172         connManager.release(endpoint3, null, TimeValue.NEG_ONE_MILLISECOND);
173         connManager.close();
174     }
175 
176     /**
177      * Tests releasing with time limits.
178      */
179     @Test
180     public void testReleaseConnectionWithTimeLimits() throws Exception {
181         final ClassicTestServer server = startServer();
182         server.registerHandler("/random/*", new RandomHandler());
183         final HttpHost target = targetHost();
184 
185         startClient();
186 
187         final PoolingHttpClientConnectionManager connManager = testResources.connManager();
188         connManager.setMaxTotal(1);
189 
190         final HttpRoute route = new HttpRoute(target, null, false);
191         final int rsplen = 8;
192         final String uri = "/random/" + rsplen;
193 
194         final ClassicHttpRequest request = new BasicClassicHttpRequest("GET", target, uri);
195         final HttpClientContext context = HttpClientContext.create();
196 
197         final LeaseRequest leaseRequest1 = connManager.lease("id1", route, null);
198         final ConnectionEndpoint endpoint1 = leaseRequest1.get(Timeout.ZERO_MILLISECONDS);
199         connManager.connect(endpoint1, null, context);
200 
201         try (final ClassicHttpResponse response1 = endpoint1.execute("id1", request, exec, context)) {
202             Assertions.assertEquals(HttpStatus.SC_OK, response1.getCode());
203         }
204 
205         // check that there is no auto-release by default
206         final LeaseRequest leaseRequest2 = connManager.lease("id2", route, null);
207         // this should fail quickly, connection has not been released
208         Assertions.assertThrows(TimeoutException.class, () -> leaseRequest2.get(Timeout.ofMilliseconds(10)));
209 
210         endpoint1.close();
211         connManager.release(endpoint1, null, TimeValue.ofMilliseconds(100));
212 
213         final LeaseRequest leaseRequest3 = connManager.lease("id2", route, null);
214         final ConnectionEndpoint endpoint2 = leaseRequest3.get(Timeout.ZERO_MILLISECONDS);
215         Assertions.assertFalse(endpoint2.isConnected());
216 
217         connManager.connect(endpoint2, null, context);
218 
219         try (final ClassicHttpResponse response2 = endpoint2.execute("id2", request, exec, context)) {
220             Assertions.assertEquals(HttpStatus.SC_OK, response2.getCode());
221         }
222 
223         connManager.release(endpoint2, null, TimeValue.ofMilliseconds(100));
224 
225         final LeaseRequest leaseRequest4 = connManager.lease("id3", route, null);
226         final ConnectionEndpoint endpoint3 = leaseRequest4.get(Timeout.ZERO_MILLISECONDS);
227         Assertions.assertTrue(endpoint3.isConnected());
228 
229         // repeat the communication, no need to prepare the request again
230         try (final ClassicHttpResponse response3 = endpoint3.execute("id3", request, exec, context)) {
231             Assertions.assertEquals(HttpStatus.SC_OK, response3.getCode());
232         }
233 
234         connManager.release(endpoint3, null, TimeValue.ofMilliseconds(100));
235         Thread.sleep(150);
236 
237         final LeaseRequest leaseRequest5 = connManager.lease("id4", route, null);
238         final ConnectionEndpoint endpoint4 = leaseRequest5.get(Timeout.ZERO_MILLISECONDS);
239         Assertions.assertFalse(endpoint4.isConnected());
240 
241         // repeat the communication, no need to prepare the request again
242         connManager.connect(endpoint4, null, context);
243 
244         try (final ClassicHttpResponse response4 = endpoint4.execute("id4", request, exec, context)) {
245             Assertions.assertEquals(HttpStatus.SC_OK, response4.getCode());
246         }
247 
248         connManager.close();
249     }
250 
251     @Test
252     public void testCloseExpiredIdleConnections() throws Exception {
253         startServer();
254         final HttpHost target = targetHost();
255         startClient();
256 
257         final PoolingHttpClientConnectionManager connManager = testResources.connManager();
258         connManager.setMaxTotal(1);
259 
260         final HttpRoute route = new HttpRoute(target, null, false);
261         final HttpClientContext context = HttpClientContext.create();
262 
263         final LeaseRequest leaseRequest1 = connManager.lease("id1", route, null);
264         final ConnectionEndpoint endpoint1 = leaseRequest1.get(Timeout.ZERO_MILLISECONDS);
265         connManager.connect(endpoint1, null, context);
266 
267         Assertions.assertEquals(1, connManager.getTotalStats().getLeased());
268         Assertions.assertEquals(1, connManager.getStats(route).getLeased());
269 
270         connManager.release(endpoint1, null, TimeValue.ofMilliseconds(100));
271 
272         // Released, still active.
273         Assertions.assertEquals(1, connManager.getTotalStats().getAvailable());
274         Assertions.assertEquals(1, connManager.getStats(route).getAvailable());
275 
276         connManager.closeExpired();
277 
278         // Time has not expired yet.
279         Assertions.assertEquals(1, connManager.getTotalStats().getAvailable());
280         Assertions.assertEquals(1, connManager.getStats(route).getAvailable());
281 
282         Thread.sleep(150);
283 
284         connManager.closeExpired();
285 
286         // Time expired now, connections are destroyed.
287         Assertions.assertEquals(0, connManager.getTotalStats().getAvailable());
288         Assertions.assertEquals(0, connManager.getStats(route).getAvailable());
289 
290         connManager.close();
291     }
292 
293     @Test
294     public void testCloseExpiredTTLConnections() throws Exception {
295         final ClassicTestServer server = startServer();
296         server.registerHandler("/random/*", new RandomHandler());
297         final HttpHost target = targetHost();
298 
299         testResources.startClient(
300                 builder -> builder
301                         .setPoolConcurrencyPolicy(PoolConcurrencyPolicy.STRICT)
302                         .setConnPoolPolicy(PoolReusePolicy.LIFO)
303                         .setDefaultConnectionConfig(ConnectionConfig.custom()
304                                 .setTimeToLive(TimeValue.ofMilliseconds(100))
305                                 .build())
306                         .setMaxConnTotal(1),
307                 builder -> {}
308         );
309 
310         final PoolingHttpClientConnectionManager connManager = testResources.connManager();
311         connManager.setMaxTotal(1);
312 
313         final HttpRoute route = new HttpRoute(target, null, false);
314         final HttpClientContext context = HttpClientContext.create();
315 
316         final LeaseRequest leaseRequest1 = connManager.lease("id1", route, null);
317         final ConnectionEndpoint endpoint1 = leaseRequest1.get(Timeout.ZERO_MILLISECONDS);
318         connManager.connect(endpoint1, null, context);
319 
320         Assertions.assertEquals(1, connManager.getTotalStats().getLeased());
321         Assertions.assertEquals(1, connManager.getStats(route).getLeased());
322         // Release, let remain idle for forever
323         connManager.release(endpoint1, null, TimeValue.NEG_ONE_MILLISECOND);
324 
325         // Released, still active.
326         Assertions.assertEquals(1, connManager.getTotalStats().getAvailable());
327         Assertions.assertEquals(1, connManager.getStats(route).getAvailable());
328 
329         connManager.closeExpired();
330 
331         // Time has not expired yet.
332         Assertions.assertEquals(1, connManager.getTotalStats().getAvailable());
333         Assertions.assertEquals(1, connManager.getStats(route).getAvailable());
334 
335         Thread.sleep(150);
336 
337         connManager.closeExpired();
338 
339         // TTL expired now, connections are destroyed.
340         Assertions.assertEquals(0, connManager.getTotalStats().getAvailable());
341         Assertions.assertEquals(0, connManager.getStats(route).getAvailable());
342 
343         connManager.close();
344     }
345 
346 }