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