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.http.impl.client.integration;
28  
29  import java.io.IOException;
30  
31  import org.apache.http.HttpClientConnection;
32  import org.apache.http.HttpException;
33  import org.apache.http.HttpHost;
34  import org.apache.http.HttpRequest;
35  import org.apache.http.HttpResponse;
36  import org.apache.http.HttpStatus;
37  import org.apache.http.client.HttpClient;
38  import org.apache.http.client.UserTokenHandler;
39  import org.apache.http.client.methods.HttpGet;
40  import org.apache.http.client.protocol.HttpClientContext;
41  import org.apache.http.entity.StringEntity;
42  import org.apache.http.localserver.LocalServerTestBase;
43  import org.apache.http.protocol.BasicHttpContext;
44  import org.apache.http.protocol.HttpContext;
45  import org.apache.http.protocol.HttpRequestHandler;
46  import org.apache.http.util.EntityUtils;
47  import org.junit.Assert;
48  import org.junit.Test;
49  
50  /**
51   * Test cases for state-ful connections.
52   */
53  public class TestStatefulConnManagement extends LocalServerTestBase {
54  
55      private static class SimpleService implements HttpRequestHandler {
56  
57          public SimpleService() {
58              super();
59          }
60  
61          @Override
62          public void handle(
63                  final HttpRequest request,
64                  final HttpResponse response,
65                  final HttpContext context) throws HttpException, IOException {
66              response.setStatusCode(HttpStatus.SC_OK);
67              final StringEntity entity = new StringEntity("Whatever");
68              response.setEntity(entity);
69          }
70      }
71  
72      @Test
73      public void testStatefulConnections() throws Exception {
74  
75          final int workerCount = 5;
76          final int requestCount = 5;
77  
78          this.serverBootstrap.registerHandler("*", new SimpleService());
79  
80          this.connManager.setMaxTotal(workerCount);
81          this.connManager.setDefaultMaxPerRoute(workerCount);
82  
83          final UserTokenHandler userTokenHandler = new UserTokenHandler() {
84  
85              @Override
86              public Object getUserToken(final HttpContext context) {
87                  final String id = (String) context.getAttribute("user");
88                  return id;
89              }
90  
91          };
92          this.clientBuilder.setUserTokenHandler(userTokenHandler);
93  
94          final HttpHost target = start();
95  
96          final HttpClientContext[] contexts = new HttpClientContext[workerCount];
97          final HttpWorker[] workers = new HttpWorker[workerCount];
98          for (int i = 0; i < contexts.length; i++) {
99              final HttpClientContext context = HttpClientContext.create();
100             contexts[i] = context;
101             workers[i] = new HttpWorker(
102                     "user" + i,
103                     context, requestCount, target, this.httpclient);
104         }
105 
106         for (final HttpWorker worker : workers) {
107             worker.start();
108         }
109         for (final HttpWorker worker : workers) {
110             worker.join(10000);
111         }
112         for (final HttpWorker worker : workers) {
113             final Exception ex = worker.getException();
114             if (ex != null) {
115                 throw ex;
116             }
117             Assert.assertEquals(requestCount, worker.getCount());
118         }
119 
120         for (final HttpContext context : contexts) {
121             final String uid = (String) context.getAttribute("user");
122 
123             for (int r = 0; r < requestCount; r++) {
124                 final String state = (String) context.getAttribute("r" + r);
125                 Assert.assertNotNull(state);
126                 Assert.assertEquals(uid, state);
127             }
128         }
129 
130     }
131 
132     static class HttpWorker extends Thread {
133 
134         private final String uid;
135         private final HttpClientContext context;
136         private final int requestCount;
137         private final HttpHost target;
138         private final HttpClient httpclient;
139 
140         private volatile Exception exception;
141         private volatile int count;
142 
143         public HttpWorker(
144                 final String uid,
145                 final HttpClientContext context,
146                 final int requestCount,
147                 final HttpHost target,
148                 final HttpClient httpclient) {
149             super();
150             this.uid = uid;
151             this.context = context;
152             this.requestCount = requestCount;
153             this.target = target;
154             this.httpclient = httpclient;
155             this.count = 0;
156         }
157 
158         public int getCount() {
159             return this.count;
160         }
161 
162         public Exception getException() {
163             return this.exception;
164         }
165 
166         @Override
167         public void run() {
168             try {
169                 this.context.setAttribute("user", this.uid);
170                 for (int r = 0; r < this.requestCount; r++) {
171                     final HttpGet httpget = new HttpGet("/");
172                     final HttpResponse response = this.httpclient.execute(
173                             this.target,
174                             httpget,
175                             this.context);
176                     this.count++;
177 
178                     final HttpClientConnection conn = this.context.getConnection(HttpClientConnection.class);
179                     final HttpContext connContext = (HttpContext) conn;
180                     String connuid = (String) connContext.getAttribute("user");
181                     if (connuid == null) {
182                         connContext.setAttribute("user", this.uid);
183                         connuid = this.uid;
184                     }
185                     this.context.setAttribute("r" + r, connuid);
186                     EntityUtils.consume(response.getEntity());
187                 }
188 
189             } catch (final Exception ex) {
190                 this.exception = ex;
191             }
192         }
193 
194     }
195 
196     @Test
197     public void testRouteSpecificPoolRecylcing() throws Exception {
198         // This tests what happens when a maxed connection pool needs
199         // to kill the last idle connection to a route to build a new
200         // one to the same route.
201 
202         final int maxConn = 2;
203 
204         this.serverBootstrap.registerHandler("*", new SimpleService());
205 
206         this.connManager.setMaxTotal(maxConn);
207         this.connManager.setDefaultMaxPerRoute(maxConn);
208 
209         final UserTokenHandler userTokenHandler = new UserTokenHandler() {
210 
211             @Override
212             public Object getUserToken(final HttpContext context) {
213                 return context.getAttribute("user");
214             }
215 
216         };
217 
218         this.clientBuilder.setUserTokenHandler(userTokenHandler);
219 
220         final HttpHost target = start();
221 
222         // Bottom of the pool : a *keep alive* connection to Route 1.
223         final HttpContext context1 = new BasicHttpContext();
224         context1.setAttribute("user", "stuff");
225         final HttpResponse response1 = this.httpclient.execute(
226                 target, new HttpGet("/"), context1);
227         EntityUtils.consume(response1.getEntity());
228 
229         // The ConnPoolByRoute now has 1 free connection, out of 2 max
230         // The ConnPoolByRoute has one RouteSpcfcPool, that has one free connection
231         // for [localhost][stuff]
232 
233         Thread.sleep(100);
234 
235         // Send a very simple HTTP get (it MUST be simple, no auth, no proxy, no 302, no 401, ...)
236         // Send it to another route. Must be a keepalive.
237         final HttpContext context2 = new BasicHttpContext();
238         final HttpResponse response2 = this.httpclient.execute(
239                 new HttpHost("127.0.0.1", this.server.getLocalPort()), new HttpGet("/"), context2);
240         EntityUtils.consume(response2.getEntity());
241         // ConnPoolByRoute now has 2 free connexions, out of its 2 max.
242         // The [localhost][stuff] RouteSpcfcPool is the same as earlier
243         // And there is a [127.0.0.1][null] pool with 1 free connection
244 
245         Thread.sleep(100);
246 
247         // This will put the ConnPoolByRoute to the targeted state :
248         // [localhost][stuff] will not get reused because this call is [localhost][null]
249         // So the ConnPoolByRoute will need to kill one connection (it is maxed out globally).
250         // The killed conn is the oldest, which means the first HTTPGet ([localhost][stuff]).
251         // When this happens, the RouteSpecificPool becomes empty.
252         final HttpContext context3 = new BasicHttpContext();
253         final HttpResponse response3 = this.httpclient.execute(
254                 target, new HttpGet("/"), context3);
255 
256         // If the ConnPoolByRoute did not behave coherently with the RouteSpecificPool
257         // this may fail. Ex : if the ConnPool discared the route pool because it was empty,
258         // but still used it to build the request3 connection.
259         EntityUtils.consume(response3.getEntity());
260 
261     }
262 
263 }