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.net.URI;
32  
33  import org.apache.hc.client5.http.classic.methods.HttpGet;
34  import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
35  import org.apache.hc.core5.http.ClassicHttpResponse;
36  import org.apache.hc.core5.http.EntityDetails;
37  import org.apache.hc.core5.http.Header;
38  import org.apache.hc.core5.http.HeaderElements;
39  import org.apache.hc.core5.http.HttpException;
40  import org.apache.hc.core5.http.HttpHeaders;
41  import org.apache.hc.core5.http.HttpHost;
42  import org.apache.hc.core5.http.HttpResponse;
43  import org.apache.hc.core5.http.HttpResponseInterceptor;
44  import org.apache.hc.core5.http.impl.HttpProcessors;
45  import org.apache.hc.core5.http.io.entity.EntityUtils;
46  import org.apache.hc.core5.http.protocol.HttpContext;
47  import org.apache.hc.core5.http.protocol.HttpProcessor;
48  import org.junit.Assert;
49  import org.junit.Test;
50  
51  public class TestConnectionReuse extends LocalServerTestBase {
52  
53      @Test
54      public void testReuseOfPersistentConnections() throws Exception {
55          this.connManager.setMaxTotal(5);
56          this.connManager.setDefaultMaxPerRoute(5);
57  
58          final HttpHost target = start();
59  
60          final WorkerThread[] workers = new WorkerThread[10];
61          for (int i = 0; i < workers.length; i++) {
62              workers[i] = new WorkerThread(
63                      this.httpclient,
64                      target,
65                      new URI("/random/2000"),
66                      10, false);
67          }
68  
69          for (final WorkerThread worker : workers) {
70              worker.start();
71          }
72          for (final WorkerThread worker : workers) {
73              worker.join(10000);
74              final Exception ex = worker.getException();
75              if (ex != null) {
76                  throw ex;
77              }
78          }
79  
80          // Expect some connection in the pool
81          Assert.assertTrue(this.connManager.getTotalStats().getAvailable() > 0);
82      }
83  
84      private static class AlwaysCloseConn implements HttpResponseInterceptor {
85  
86          @Override
87          public void process(
88                  final HttpResponse response,
89                  final EntityDetails entityDetails,
90                  final HttpContext context) throws HttpException, IOException {
91              response.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE);
92          }
93  
94      }
95  
96      @Test
97      public void testReuseOfClosedConnections() throws Exception {
98          this.connManager.setMaxTotal(5);
99          this.connManager.setDefaultMaxPerRoute(5);
100 
101         final HttpProcessor httpproc = HttpProcessors.customServer(null)
102                 .add(new AlwaysCloseConn())
103                 .build();
104         final HttpHost target = start(httpproc, null);
105 
106         final WorkerThread[] workers = new WorkerThread[10];
107         for (int i = 0; i < workers.length; i++) {
108             workers[i] = new WorkerThread(
109                     this.httpclient,
110                     target,
111                     new URI("/random/2000"),
112                     10, false);
113         }
114 
115         for (final WorkerThread worker : workers) {
116             worker.start();
117         }
118         for (final WorkerThread worker : workers) {
119             worker.join(10000);
120             final Exception ex = worker.getException();
121             if (ex != null) {
122                 throw ex;
123             }
124         }
125 
126         // Expect zero connections in the pool
127         Assert.assertEquals(0, this.connManager.getTotalStats().getAvailable());
128     }
129 
130     @Test
131     public void testReuseOfAbortedConnections() throws Exception {
132         this.connManager.setMaxTotal(5);
133         this.connManager.setDefaultMaxPerRoute(5);
134 
135         final HttpHost target = start();
136 
137         final WorkerThread[] workers = new WorkerThread[10];
138         for (int i = 0; i < workers.length; i++) {
139             workers[i] = new WorkerThread(
140                     this.httpclient,
141                     target,
142                     new URI("/random/2000"),
143                     10, true);
144         }
145 
146         for (final WorkerThread worker : workers) {
147             worker.start();
148         }
149         for (final WorkerThread worker : workers) {
150             worker.join(10000);
151             final Exception ex = worker.getException();
152             if (ex != null) {
153                 throw ex;
154             }
155         }
156 
157         // Expect zero connections in the pool
158         Assert.assertEquals(0, this.connManager.getTotalStats().getAvailable());
159     }
160 
161     @Test
162     public void testKeepAliveHeaderRespected() throws Exception {
163         this.connManager.setMaxTotal(1);
164         this.connManager.setDefaultMaxPerRoute(1);
165 
166         final HttpProcessor httpproc = HttpProcessors.customServer(null)
167                 .add(new ResponseKeepAlive())
168                 .build();
169         final HttpHost target = start(httpproc, null);
170 
171         ClassicHttpResponse response = this.httpclient.execute(target, new HttpGet("/random/2000"));
172         EntityUtils.consume(response.getEntity());
173 
174         Assert.assertEquals(1, this.connManager.getTotalStats().getAvailable());
175 
176         response = this.httpclient.execute(target, new HttpGet("/random/2000"));
177         EntityUtils.consume(response.getEntity());
178 
179         Assert.assertEquals(1, this.connManager.getTotalStats().getAvailable());
180 
181         // Now sleep for 1.1 seconds and let the timeout do its work
182         Thread.sleep(1100);
183         response = this.httpclient.execute(target, new HttpGet("/random/2000"));
184         EntityUtils.consume(response.getEntity());
185 
186         Assert.assertEquals(1, this.connManager.getTotalStats().getAvailable());
187 
188         // Do another request just under the 1 second limit & make
189         // sure we reuse that connection.
190         Thread.sleep(500);
191         response = this.httpclient.execute(target, new HttpGet("/random/2000"));
192         EntityUtils.consume(response.getEntity());
193 
194         Assert.assertEquals(1, this.connManager.getTotalStats().getAvailable());
195     }
196 
197     private static class WorkerThread extends Thread {
198 
199         private final URI requestURI;
200         private final HttpHost target;
201         private final CloseableHttpClient httpclient;
202         private final int repetitions;
203         private final boolean forceClose;
204 
205         private volatile Exception exception;
206 
207         public WorkerThread(
208                 final CloseableHttpClient httpclient,
209                 final HttpHost target,
210                 final URI requestURI,
211                 final int repetitions,
212                 final boolean forceClose) {
213             super();
214             this.httpclient = httpclient;
215             this.requestURI = requestURI;
216             this.target = target;
217             this.repetitions = repetitions;
218             this.forceClose = forceClose;
219         }
220 
221         @Override
222         public void run() {
223             try {
224                 for (int i = 0; i < this.repetitions; i++) {
225                     final HttpGet httpget = new HttpGet(this.requestURI);
226                     final ClassicHttpResponse response = this.httpclient.execute(
227                             this.target,
228                             httpget);
229                     if (this.forceClose) {
230                         httpget.cancel();
231                     } else {
232                         EntityUtils.consume(response.getEntity());
233                     }
234                 }
235             } catch (final Exception ex) {
236                 this.exception = ex;
237             }
238         }
239 
240         public Exception getException() {
241             return exception;
242         }
243 
244     }
245 
246     // A very basic keep-alive header interceptor, to add Keep-Alive: timeout=1
247     // if there is no Connection: close header.
248     private static class ResponseKeepAlive implements HttpResponseInterceptor {
249         @Override
250         public void process(
251                 final HttpResponse response,
252                 final EntityDetails entityDetails,
253                 final HttpContext context) throws HttpException, IOException {
254             final Header connection = response.getFirstHeader(HttpHeaders.CONNECTION);
255             if(connection != null) {
256                 if(!connection.getValue().equalsIgnoreCase("Close")) {
257                     response.addHeader(HeaderElements.KEEP_ALIVE, "timeout=1");
258                 }
259             }
260         }
261     }
262 
263 }