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