You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2008/05/21 19:06:18 UTC

svn commit: r658759 - in /httpcomponents/httpclient/trunk: ./ module-client/src/main/java/org/apache/http/client/ module-client/src/main/java/org/apache/http/client/protocol/ module-client/src/main/java/org/apache/http/impl/client/ module-client/src/te...

Author: olegk
Date: Wed May 21 10:06:17 2008
New Revision: 658759

URL: http://svn.apache.org/viewvc?rev=658759&view=rev
Log:
Resolved a long standing problem with HttpClient not taking into account the user context when pooling / re-using connections. HttpClient now correctly handles stateful / user specific connections such as persistent NTLM connections and SSL connections with client side authentication. 

Added:
    httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/UserTokenHandler.java   (with props)
    httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultUserTokenHandler.java   (with props)
    httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/client/TestStatefulConnManagement.java   (with props)
Modified:
    httpcomponents/httpclient/trunk/RELEASE_NOTES.txt
    httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/protocol/ClientContext.java
    httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java
    httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultClientRequestDirector.java
    httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultHttpClient.java
    httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/client/TestAllHttpClientImpl.java

Modified: httpcomponents/httpclient/trunk/RELEASE_NOTES.txt
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/RELEASE_NOTES.txt?rev=658759&r1=658758&r2=658759&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/RELEASE_NOTES.txt (original)
+++ httpcomponents/httpclient/trunk/RELEASE_NOTES.txt Wed May 21 10:06:17 2008
@@ -1,6 +1,12 @@
 Changes since 4.0 Alpha 4
 -------------------
 
+* Resolved a long standing problem with HttpClient not taking into account
+  the user context when pooling / re-using connections. HttpClient now
+  correctly handles stateful / user specific connections such as persistent 
+  NTLM connections and SSL connections with client side authentication. 
+  Contributed by Oleg Kalnichevski <olegk at apache.org>
+
 * [HTTPCLIENT-773] Improved handling of the 'expires' attribute by the
   'Best Match' cookie spec.
   Contributed by Oleg Kalnichevski <olegk at apache.org>

Added: httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/UserTokenHandler.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/UserTokenHandler.java?rev=658759&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/UserTokenHandler.java (added)
+++ httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/UserTokenHandler.java Wed May 21 10:06:17 2008
@@ -0,0 +1,64 @@
+/*
+ * $HeadURL:$
+ * $Revision:$
+ * $Date:$
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.client;
+
+import org.apache.http.protocol.HttpContext;
+
+/**
+ * A handler for determining if the given execution context is user specific 
+ * or not. The token object returned by this handler is expected to uniquely 
+ * identify the current user if the context is user specific or to be 
+ * <code>null</code> if the context does not contain any resources or details 
+ * specific to the current user.
+ * <p/>
+ * The user token will be used to ensure that user specific resouces will not
+ * shared with or reused by other users.
+ *
+ * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
+ * 
+ * @since 4.0
+ */
+public interface UserTokenHandler {
+
+    /**
+     * The token object returned by this method is expected to uniquely 
+     * identify the current user if the context is user specific or to be 
+     * <code>null</code> if it is not.
+     * 
+     * @param context the execution context
+     * 
+     * @return user token that uniquely identifies the user or 
+     * <code>null</null> if the context is not user specific.
+     */
+    Object getUserToken(HttpContext context);
+
+}

Propchange: httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/UserTokenHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/UserTokenHandler.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/UserTokenHandler.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/protocol/ClientContext.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/protocol/ClientContext.java?rev=658759&r1=658758&r2=658759&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/protocol/ClientContext.java (original)
+++ httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/protocol/ClientContext.java Wed May 21 10:06:17 2008
@@ -47,5 +47,6 @@
     public static final String TARGET_AUTH_STATE     = "http.auth.target-scope"; 
     public static final String PROXY_AUTH_STATE      = "http.auth.proxy-scope";
     public static final String AUTH_SCHEME_PREF      = "http.auth.scheme-pref";
+    public static final String USER_TOKEN            = "http.user-token";
     
 }

Modified: httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java?rev=658759&r1=658758&r2=658759&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java (original)
+++ httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java Wed May 21 10:06:17 2008
@@ -49,6 +49,7 @@
 import org.apache.http.client.HttpClient;
 import org.apache.http.client.HttpRequestRetryHandler;
 import org.apache.http.client.RedirectHandler;
+import org.apache.http.client.UserTokenHandler;
 import org.apache.http.client.methods.HttpUriRequest;
 import org.apache.http.client.protocol.ClientContext;
 import org.apache.http.conn.ClientConnectionManager;
@@ -110,9 +111,12 @@
     /** The credentials provider. */
     private CredentialsProvider credsProvider;
     
-    /** The HttpRoutePlanner object. */
+    /** The route planner. */
     private HttpRoutePlanner routePlanner;
 
+    /** The user token handler. */
+    private UserTokenHandler userTokenHandler;
+
 
     /**
      * Creates a new HTTP client.
@@ -169,18 +173,8 @@
     protected abstract HttpRoutePlanner createHttpRoutePlanner();
 
     
-    public synchronized final HttpRoutePlanner getRoutePlanner() {
-        if (this.routePlanner == null) {
-            this.routePlanner = createHttpRoutePlanner();
-        }
-        return this.routePlanner;
-    }
-
+    protected abstract UserTokenHandler createUserTokenHandler();
 
-    public synchronized void setRoutePlanner(final HttpRoutePlanner routePlanner) {
-        this.routePlanner = routePlanner;
-    }
-    
     
     // non-javadoc, see interface HttpClient
     public synchronized final HttpParams getParams() {
@@ -334,6 +328,32 @@
     }
 
 
+    public synchronized final HttpRoutePlanner getRoutePlanner() {
+        if (this.routePlanner == null) {
+            this.routePlanner = createHttpRoutePlanner();
+        }
+        return this.routePlanner;
+    }
+
+
+    public synchronized void setRoutePlanner(final HttpRoutePlanner routePlanner) {
+        this.routePlanner = routePlanner;
+    }
+    
+    
+    public synchronized final UserTokenHandler getUserTokenHandler() {
+        if (this.userTokenHandler == null) {
+            this.userTokenHandler = createUserTokenHandler();
+        }
+        return this.userTokenHandler;
+    }
+
+
+    public synchronized void setUserTokenHandler(final UserTokenHandler userTokenHandler) {
+        this.userTokenHandler = userTokenHandler;
+    }
+    
+    
     protected synchronized final BasicHttpProcessor getHttpProcessor() {
         if (httpProcessor == null) {
             httpProcessor = createHttpProcessor();
@@ -500,6 +520,7 @@
                     getRedirectHandler(),
                     getTargetAuthenticationHandler(),
                     getProxyAuthenticationHandler(),
+                    getUserTokenHandler(),
                     determineParams(request));
         }
 
@@ -524,6 +545,7 @@
             final RedirectHandler redirectHandler,
             final AuthenticationHandler targetAuthHandler,
             final AuthenticationHandler proxyAuthHandler,
+            final UserTokenHandler stateHandler,
             final HttpParams params) {
         return new DefaultClientRequestDirector(
                 conman,
@@ -534,6 +556,7 @@
                 redirectHandler,
                 targetAuthHandler,
                 proxyAuthHandler,
+                stateHandler,
                 params);
     }
 

Modified: httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultClientRequestDirector.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultClientRequestDirector.java?rev=658759&r1=658758&r2=658759&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultClientRequestDirector.java (original)
+++ httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultClientRequestDirector.java Wed May 21 10:06:17 2008
@@ -63,6 +63,7 @@
 import org.apache.http.client.HttpRequestRetryHandler;
 import org.apache.http.client.RedirectException;
 import org.apache.http.client.RedirectHandler;
+import org.apache.http.client.UserTokenHandler;
 import org.apache.http.client.methods.AbortableHttpRequest;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.params.ClientPNames;
@@ -134,6 +135,9 @@
     /** The proxy authentication handler. */
     private final AuthenticationHandler proxyAuthHandler;
     
+    /** The user token handler. */
+    private final UserTokenHandler userTokenHandler;
+    
     /** The HTTP parameters. */
     protected final HttpParams params;
     
@@ -157,6 +161,7 @@
             final RedirectHandler redirectHandler,
             final AuthenticationHandler targetAuthHandler,
             final AuthenticationHandler proxyAuthHandler,
+            final UserTokenHandler userTokenHandler,
             final HttpParams params) {
 
         if (conman == null) {
@@ -191,6 +196,10 @@
             throw new IllegalArgumentException
                 ("Proxy authentication handler may not be null.");
         }
+        if (userTokenHandler == null) {
+            throw new IllegalArgumentException
+                ("User token handler may not be null.");
+        }
         if (params == null) {
             throw new IllegalArgumentException
                 ("HTTP parameters may not be null");
@@ -203,6 +212,7 @@
         this.redirectHandler   = redirectHandler;
         this.targetAuthHandler = targetAuthHandler;
         this.proxyAuthHandler  = proxyAuthHandler;
+        this.userTokenHandler  = userTokenHandler; 
         this.params            = params;
         this.requestExec       = new HttpRequestExecutor();
 
@@ -283,10 +293,13 @@
                 RequestWrapper wrapper = roureq.getRequest();
                 HttpRoute route = roureq.getRoute();
                 
+                // See if we have a user token bound to the execution context
+                Object userToken = context.getAttribute(ClientContext.USER_TOKEN);
+                
                 // Allocate connection if needed
                 if (managedConn == null) {
                     ClientConnectionRequest connRequest = connManager.requestConnection(
-                            route, null);
+                            route, userToken);
                     if (orig instanceof AbortableHttpRequest) {
                         ((AbortableHttpRequest) orig).setConnectionRequest(connRequest);
                     }
@@ -415,6 +428,12 @@
                     followup.getRequest().clearHeaders();
                     roureq = followup;
                 }
+                
+                userToken = this.userTokenHandler.getUserToken(context);
+                context.setAttribute(ClientContext.USER_TOKEN, userToken);
+                if (managedConn != null) {
+                    managedConn.setState(userToken);
+                }
             } // while not done
 
             // The connection is in or can be brought to a re-usable state.

Modified: httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultHttpClient.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultHttpClient.java?rev=658759&r1=658758&r2=658759&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultHttpClient.java (original)
+++ httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultHttpClient.java Wed May 21 10:06:17 2008
@@ -39,6 +39,7 @@
 import org.apache.http.client.CredentialsProvider;
 import org.apache.http.client.HttpRequestRetryHandler;
 import org.apache.http.client.RedirectHandler;
+import org.apache.http.client.UserTokenHandler;
 import org.apache.http.client.params.AuthPolicy;
 import org.apache.http.client.params.ClientPNames;
 import org.apache.http.client.params.CookiePolicy;
@@ -289,11 +290,16 @@
     }
 
 
-    // non-javadoc, see base class AbstractHttpClient
     @Override
     protected HttpRoutePlanner createHttpRoutePlanner() {
         return new DefaultHttpRoutePlanner
             (getConnectionManager().getSchemeRegistry());
     }
+
+
+    @Override
+    protected UserTokenHandler createUserTokenHandler() {
+        return new DefaultUserTokenHandler();
+    }
     
 } // class DefaultHttpClient

Added: httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultUserTokenHandler.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultUserTokenHandler.java?rev=658759&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultUserTokenHandler.java (added)
+++ httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultUserTokenHandler.java Wed May 21 10:06:17 2008
@@ -0,0 +1,88 @@
+/*
+ * $HeadURL:$
+ * $Revision:$
+ * $Date:$
+ *
+ * ====================================================================
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.client;
+
+import java.security.Principal;
+
+import javax.net.ssl.SSLSession;
+
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.Credentials;
+import org.apache.http.client.AuthState;
+import org.apache.http.client.UserTokenHandler;
+import org.apache.http.client.protocol.ClientContext;
+import org.apache.http.conn.ManagedClientConnection;
+import org.apache.http.protocol.ExecutionContext;
+import org.apache.http.protocol.HttpContext;
+
+public class DefaultUserTokenHandler implements UserTokenHandler {
+
+    public Object getUserToken(final HttpContext context) {
+        
+        Principal userPrincipal = null;
+        
+        AuthState targetAuthState = (AuthState) context.getAttribute(
+                ClientContext.TARGET_AUTH_STATE);
+        if (targetAuthState != null) {
+            userPrincipal = getAuthPrincipal(targetAuthState);
+            if (userPrincipal == null) {
+                AuthState proxyAuthState = (AuthState) context.getAttribute(
+                        ClientContext.PROXY_AUTH_STATE);
+                userPrincipal = getAuthPrincipal(proxyAuthState);
+            }
+        }
+        
+        if (userPrincipal == null) {
+            ManagedClientConnection conn = (ManagedClientConnection) context.getAttribute(
+                    ExecutionContext.HTTP_CONNECTION);
+            if (conn.isOpen()) {
+                SSLSession sslsession = conn.getSSLSession();
+                if (sslsession != null) {
+                    userPrincipal = sslsession.getLocalPrincipal();
+                }
+            }
+        }
+        
+        return userPrincipal;
+    }
+
+    private static Principal getAuthPrincipal(final AuthState authState) {
+        AuthScheme scheme = authState.getAuthScheme();
+        if (scheme != null && scheme.isComplete() && scheme.isConnectionBased()) {
+            Credentials creds = authState.getCredentials();
+            if (creds != null) {
+                return creds.getUserPrincipal(); 
+            }
+        }
+        return null;
+    }
+    
+}

Propchange: httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultUserTokenHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultUserTokenHandler.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultUserTokenHandler.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/client/TestAllHttpClientImpl.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/client/TestAllHttpClientImpl.java?rev=658759&r1=658758&r2=658759&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/client/TestAllHttpClientImpl.java (original)
+++ httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/client/TestAllHttpClientImpl.java Wed May 21 10:06:17 2008
@@ -45,6 +45,7 @@
         suite.addTest(TestBasicCredentialsProvider.suite());
         suite.addTest(TestRequestWrapper.suite());
         suite.addTest(TestDefaultClientRequestDirector.suite());
+        suite.addTest(TestStatefulConnManagement.suite());
         return suite;
     }
 

Added: httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/client/TestStatefulConnManagement.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/client/TestStatefulConnManagement.java?rev=658759&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/client/TestStatefulConnManagement.java (added)
+++ httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/client/TestStatefulConnManagement.java Wed May 21 10:06:17 2008
@@ -0,0 +1,219 @@
+/*
+ * $HeadURL$
+ * $Revision$
+ * $Date$
+ * ====================================================================
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.http.impl.client;
+
+import java.io.IOException;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.UserTokenHandler;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.params.HttpClientParams;
+import org.apache.http.conn.ManagedClientConnection;
+import org.apache.http.conn.params.ConnPerRouteBean;
+import org.apache.http.conn.params.HttpConnectionManagerParams;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.localserver.ServerTestBase;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.ExecutionContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpRequestHandler;
+
+/**
+ * Unit tests for {@link DefaultClientRequestDirector}
+ */
+public class TestStatefulConnManagement extends ServerTestBase {
+
+    public TestStatefulConnManagement(final String testName) throws IOException {
+        super(testName);
+    }
+
+    public static void main(String args[]) {
+        String[] testCaseName = { TestStatefulConnManagement.class.getName() };
+        junit.textui.TestRunner.main(testCaseName);
+    }
+
+    public static Test suite() {
+        return new TestSuite(TestStatefulConnManagement.class);
+    }
+    
+    private class SimpleService implements HttpRequestHandler {
+        
+        public SimpleService() {
+            super();
+        }
+
+        public void handle(
+                final HttpRequest request, 
+                final HttpResponse response, 
+                final HttpContext context) throws HttpException, IOException {
+            response.setStatusCode(HttpStatus.SC_OK);
+            StringEntity entity = new StringEntity("Whatever");
+            response.setEntity(entity);
+        }
+    }
+
+    public void testStatefulConnections() throws Exception {
+
+        int workerCount = 5;
+        int requestCount = 5;
+        
+        int port = this.localServer.getServicePort();
+        this.localServer.register("*", new SimpleService());
+
+        HttpHost target = new HttpHost("localhost", port);
+        
+        HttpParams params = defaultParams.copy();
+        HttpConnectionManagerParams.setMaxTotalConnections(params, workerCount);
+        HttpConnectionManagerParams.setMaxConnectionsPerRoute(params, 
+                new ConnPerRouteBean(workerCount));
+        HttpClientParams.setConnectionManagerTimeout(params, 10L);
+        
+        ThreadSafeClientConnManager mgr = new ThreadSafeClientConnManager(
+                params, supportedSchemes);        
+        
+        DefaultHttpClient client = new DefaultHttpClient(mgr, params); 
+        
+        HttpContext[] contexts = new HttpContext[workerCount];
+        HttpWorker[] workers = new HttpWorker[workerCount];
+        for (int i = 0; i < contexts.length; i++) {
+            HttpContext context = new BasicHttpContext();
+            context.setAttribute("user", Integer.valueOf(i));
+            contexts[i] = context;
+            workers[i] = new HttpWorker(context, requestCount, target, client);
+        }
+        
+        client.setUserTokenHandler(new UserTokenHandler() {
+
+            public Object getUserToken(final HttpContext context) {
+                Integer id = (Integer) context.getAttribute("user");
+                return id;
+            }
+            
+        });
+        
+        
+        for (int i = 0; i < workers.length; i++) {
+            workers[i].start();
+        }
+        for (int i = 0; i < workers.length; i++) {
+            workers[i].join(10000);
+        }
+        for (int i = 0; i < workers.length; i++) {
+            Exception ex = workers[i].getException();
+            if (ex != null) {
+                throw ex;
+            }
+            assertEquals(requestCount, workers[i].getCount());
+        }
+        
+        for (int i = 0; i < contexts.length; i++) {
+            HttpContext context = contexts[i];
+            Integer id = (Integer) context.getAttribute("user");
+            
+            for (int r = 0; r < requestCount; r++) {
+                Integer state = (Integer) context.getAttribute("r" + r);
+                assertNotNull(state);
+                assertEquals(id, state);
+            }
+        }
+        
+    }
+    
+    static class HttpWorker extends Thread {
+
+        private final HttpContext context;
+        private final int requestCount;
+        private final HttpHost target;
+        private final HttpClient httpclient;
+        
+        private volatile Exception exception;
+        private volatile int count;
+        
+        public HttpWorker(
+                final HttpContext context, 
+                int requestCount,
+                final HttpHost target,
+                final HttpClient httpclient) {
+            super();
+            this.context = context;
+            this.requestCount = requestCount;
+            this.target = target;
+            this.httpclient = httpclient;
+            this.count = 0;
+        }
+        
+        public int getCount() {
+            return this.count;
+        }
+
+        public Exception getException() {
+            return this.exception;
+        }
+
+        @Override
+        public void run() {
+            try {
+                for (int r = 0; r < this.requestCount; r++) {
+                    HttpGet httpget = new HttpGet("/");
+                    HttpResponse response = this.httpclient.execute(
+                            this.target, 
+                            httpget, 
+                            this.context);
+                    this.count++;
+
+                    ManagedClientConnection conn = (ManagedClientConnection) this.context.getAttribute(
+                            ExecutionContext.HTTP_CONNECTION);
+                    
+                    this.context.setAttribute("r" + r, conn.getState());
+                    
+                    HttpEntity entity = response.getEntity();
+                    if (entity != null) {
+                        entity.consumeContent();
+                    }
+                }
+                
+            } catch (Exception ex) {
+                this.exception = ex;
+            }
+        }
+        
+    }
+
+}

Propchange: httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/client/TestStatefulConnManagement.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/client/TestStatefulConnManagement.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/client/TestStatefulConnManagement.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain