You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jmeter.apache.org by pm...@apache.org on 2019/05/23 21:26:12 UTC

svn commit: r1859826 - in /jmeter/trunk: src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java xdocs/changes.xml

Author: pmouawad
Date: Thu May 23 21:26:12 2019
New Revision: 1859826

URL: http://svn.apache.org/viewvc?rev=1859826&view=rev
Log:
Bug 62672 - HTTP Request send double requests when configure proxy with authentication

This closes #397
Bugzilla Id: 62672

Modified:
    jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java
    jmeter/trunk/xdocs/changes.xml

Modified: jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java?rev=1859826&r1=1859825&r2=1859826&view=diff
==============================================================================
--- jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java (original)
+++ jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java Thu May 23 21:26:12 2019
@@ -46,7 +46,7 @@ import java.util.regex.Pattern;
 import javax.security.auth.Subject;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.lang3.tuple.MutableTriple;
 import org.apache.http.Header;
 import org.apache.http.HttpClientConnection;
 import org.apache.http.HttpConnectionMetrics;
@@ -65,6 +65,7 @@ import org.apache.http.auth.AuthState;
 import org.apache.http.auth.Credentials;
 import org.apache.http.auth.NTCredentials;
 import org.apache.http.client.AuthCache;
+import org.apache.http.client.AuthenticationStrategy;
 import org.apache.http.client.CredentialsProvider;
 import org.apache.http.client.config.AuthSchemes;
 import org.apache.http.client.config.CookieSpecs;
@@ -118,6 +119,7 @@ import org.apache.http.impl.client.Defau
 import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.http.impl.client.HttpClients;
 import org.apache.http.impl.client.LaxRedirectStrategy;
+import org.apache.http.impl.client.ProxyAuthenticationStrategy;
 import org.apache.http.impl.client.StandardHttpRequestRetryHandler;
 import org.apache.http.impl.conn.DefaultHttpClientConnectionOperator;
 import org.apache.http.impl.conn.DefaultSchemePortResolver;
@@ -455,7 +457,7 @@ public class HTTPHC4Impl extends HTTPHCA
     /**
      * 1 HttpClient instance per combination of (HttpClient,HttpClientKey)
      */
-    private static final ThreadLocal<Map<HttpClientKey, Pair<CloseableHttpClient, PoolingHttpClientConnectionManager>>> 
+    private static final ThreadLocal<Map<HttpClientKey, MutableTriple<CloseableHttpClient, AuthState, PoolingHttpClientConnectionManager>>>
         HTTPCLIENTS_CACHE_PER_THREAD_AND_HTTPCLIENTKEY = 
             InheritableThreadLocal.withInitial(() -> new HashMap<>(5));
 
@@ -543,8 +545,16 @@ public class HTTPHC4Impl extends HTTPHCA
         HttpContext localContext = new BasicHttpContext();
         HttpClientContext clientContext = HttpClientContext.adapt(localContext);
         clientContext.setAttribute(CONTEXT_ATTRIBUTE_AUTH_MANAGER, getAuthManager());
+        HttpClientKey key = createHttpClientKey(url);
+        MutableTriple<CloseableHttpClient, AuthState, PoolingHttpClientConnectionManager> triple;
         try {
-            httpClient = setupClient(jMeterVariables, url, clientContext);
+            httpClient = setupClient(key, jMeterVariables, clientContext);
+            // Cache triple for further use
+            Map<HttpClientKey, MutableTriple<CloseableHttpClient, AuthState, PoolingHttpClientConnectionManager>> 
+                mapHttpClientPerHttpClientKey =
+                    HTTPCLIENTS_CACHE_PER_THREAD_AND_HTTPCLIENTKEY.get();
+            triple =
+                    mapHttpClientPerHttpClientKey.get(key);
             URI uri = url.toURI();
             httpRequest = createHttpRequest(uri, method, areFollowingRedirect);
             setupRequest(url, httpRequest, res); // can throw IOException
@@ -572,6 +582,7 @@ public class HTTPHC4Impl extends HTTPHCA
             // perform the sample
             httpResponse = 
                     executeRequest(httpClient, httpRequest, localContext, url);
+            saveProxyAuth(triple, localContext);
             if (log.isDebugEnabled()) {
                 log.debug("Headers in request before:{}", Arrays.asList(httpRequest.getAllHeaders()));
             }
@@ -690,6 +701,30 @@ public class HTTPHC4Impl extends HTTPHCA
     }
 
     /**
+     * Associate Proxy state to thread
+     * @param triple {@link MutableTriple} 
+     * @param localContext {@link HttpContext}
+     */
+    private void saveProxyAuth(
+            MutableTriple<CloseableHttpClient, AuthState, PoolingHttpClientConnectionManager> triple,
+            HttpContext localContext) {
+        triple.setMiddle((AuthState) localContext.getAttribute(HttpClientContext.PROXY_AUTH_STATE));
+    }
+
+    /**
+     * Store in localContext Proxy auth state of triple 
+     * @param triple {@link MutableTriple}
+     * @param localContext {@link HttpContext}
+     */
+    private void setupProxyAuth(MutableTriple<CloseableHttpClient, AuthState, PoolingHttpClientConnectionManager> triple,
+                                HttpContext localContext) {
+        if (triple != null) {
+            AuthState proxy = triple.getMiddle();
+            localContext.setAttribute(HttpClientContext.PROXY_AUTH_STATE, proxy);
+        }
+    }
+
+    /**
      * @param uri {@link URI}
      * @param method HTTP Method
      * @param areFollowingRedirect Are we following redirects
@@ -836,7 +871,7 @@ public class HTTPHC4Impl extends HTTPHCA
                                     httpClient.execute(httpRequest, localContext));
                 } catch (PrivilegedActionException e) {
                     log.error("Can't execute httpRequest with subject: {}", subject, e);
-                    throw new RuntimeException("Can't execute httpRequest with subject:" + subject, e);
+                    throw new IllegalArgumentException("Can't execute httpRequest with subject:" + subject, e);
                 }
             }
         }
@@ -956,47 +991,22 @@ public class HTTPHC4Impl extends HTTPHCA
         }
     }
 
-    private CloseableHttpClient setupClient(JMeterVariables jMeterVariables, URL url, HttpClientContext clientContext) throws GeneralSecurityException {
-
-        Map<HttpClientKey, Pair<CloseableHttpClient, PoolingHttpClientConnectionManager>> mapHttpClientPerHttpClientKey = 
+    private CloseableHttpClient setupClient(HttpClientKey key, JMeterVariables jMeterVariables, HttpClientContext clientContext) throws GeneralSecurityException {
+        Map<HttpClientKey, MutableTriple<CloseableHttpClient, AuthState, PoolingHttpClientConnectionManager>> mapHttpClientPerHttpClientKey =
                 HTTPCLIENTS_CACHE_PER_THREAD_AND_HTTPCLIENTKEY.get();
-        
-        final String host = url.getHost();
-        String proxyScheme = getProxyScheme();
-        String proxyHost = getProxyHost();
-        int proxyPort = getProxyPortInt();
-        String proxyPass = getProxyPass();
-        String proxyUser = getProxyUser();
-
-        // static proxy is the globally define proxy eg command line or properties
-        boolean useStaticProxy = isStaticProxy(host);
-        // dynamic proxy is the proxy defined for this sampler
-        boolean useDynamicProxy = isDynamicProxy(proxyHost, proxyPort);
-        boolean useProxy = useStaticProxy || useDynamicProxy;
-        
-        // if both dynamic and static are used, the dynamic proxy has priority over static
-        if(!useDynamicProxy) {
-            proxyScheme = PROXY_SCHEME;
-            proxyHost = PROXY_HOST;
-            proxyPort = PROXY_PORT;
-            proxyUser = PROXY_USER;
-            proxyPass = PROXY_PASS;
-        }
-
-        // Lookup key - must agree with all the values used to create the HttpClient.
-        HttpClientKey key = new HttpClientKey(url, useProxy, proxyScheme, proxyHost, proxyPort, proxyUser, proxyPass);
         clientContext.setAttribute(CONTEXT_ATTRIBUTE_CLIENT_KEY, key);
         CloseableHttpClient httpClient = null;
         boolean concurrentDwn = this.testElement.isConcurrentDwn();
         if(concurrentDwn) {
             httpClient = (CloseableHttpClient) JMeterContextService.getContext().getSamplerContext().get(CONTEXT_ATTRIBUTE_HTTPCLIENT_TOKEN);
         }
-        Pair<CloseableHttpClient, PoolingHttpClientConnectionManager> pair = mapHttpClientPerHttpClientKey.get(key);
+        MutableTriple<CloseableHttpClient, AuthState, PoolingHttpClientConnectionManager> triple = 
+                mapHttpClientPerHttpClientKey.get(key);
         if (httpClient == null) {
-            httpClient = pair != null ? pair.getLeft() : null;
+            httpClient = triple != null ? triple.getLeft() : null;
         }
-
-        resetStateIfNeeded(jMeterVariables, clientContext, mapHttpClientPerHttpClientKey);
+        setupProxyAuth(triple, clientContext);
+        resetStateIfNeeded(triple, jMeterVariables, clientContext, mapHttpClientPerHttpClientKey);
 
         if (httpClient == null) { // One-time init for this client
             DnsResolver resolver = this.testElement.getDNSResolver();
@@ -1041,7 +1051,8 @@ public class HTTPHC4Impl extends HTTPHCA
                     setRedirectStrategy(new LaxRedirectStrategy()).
                     setConnectionTimeToLive(TIME_TO_LIVE, TimeUnit.MILLISECONDS).
                     setRetryHandler(new StandardHttpRequestRetryHandler(RETRY_COUNT, REQUEST_SENT_RETRY_ENABLED)).
-                    setConnectionReuseStrategy(DefaultClientConnectionReuseStrategy.INSTANCE);
+                    setConnectionReuseStrategy(DefaultClientConnectionReuseStrategy.INSTANCE).
+                    setProxyAuthenticationStrategy(getProxyAuthStrategy());
             if(DISABLE_DEFAULT_UA) {
                 builder.disableDefaultUserAgent();
             }
@@ -1062,15 +1073,15 @@ public class HTTPHC4Impl extends HTTPHCA
             }
 
             // Set up proxy details
-            if(useProxy) {
-                HttpHost proxy = new HttpHost(proxyHost, proxyPort, proxyScheme);
+            if (key.hasProxy) {
+                HttpHost proxy = new HttpHost(key.proxyHost, key.proxyPort, key.proxyScheme);
                 builder.setProxy(proxy);
                 
                 CredentialsProvider credsProvider = new BasicCredentialsProvider();
-                if (proxyUser.length() > 0) {
+                if (!key.proxyUser.isEmpty()) {
                     credsProvider.setCredentials(
-                        new AuthScope(proxyHost, proxyPort),
-                        new NTCredentials(proxyUser, proxyPass, LOCALHOST, PROXY_DOMAIN));
+                            new AuthScope(key.proxyHost, key.proxyPort),
+                            new NTCredentials(key.proxyUser, key.proxyPass, LOCALHOST, PROXY_DOMAIN));
                 }
                 builder.setDefaultCredentialsProvider(credsProvider);
             }
@@ -1082,7 +1093,7 @@ public class HTTPHC4Impl extends HTTPHCA
             if (log.isDebugEnabled()) {
                 log.debug("Created new HttpClient: @{} {}", System.identityHashCode(httpClient), key);
             }
-            mapHttpClientPerHttpClientKey.put(key, Pair.of(httpClient, pHCCM)); // save the agent for next time round
+            mapHttpClientPerHttpClientKey.put(key, MutableTriple.of(httpClient, null, pHCCM)); // save the agent for next time round
         } else {
             if (log.isDebugEnabled()) {
                 log.debug("Reusing the HttpClient: @{} {}", System.identityHashCode(httpClient),key);
@@ -1095,6 +1106,37 @@ public class HTTPHC4Impl extends HTTPHCA
         return httpClient;
     }
 
+    protected AuthenticationStrategy getProxyAuthStrategy() {
+        return ProxyAuthenticationStrategy.INSTANCE;
+    }
+
+    private HttpClientKey createHttpClientKey(URL url) {
+        final String host = url.getHost();
+        String proxyScheme = getProxyScheme();
+        String proxyHost = getProxyHost();
+        int proxyPort = getProxyPortInt();
+        String proxyPass = getProxyPass();
+        String proxyUser = getProxyUser();
+
+        // static proxy is the globally define proxy eg command line or properties
+        boolean useStaticProxy = isStaticProxy(host);
+        // dynamic proxy is the proxy defined for this sampler
+        boolean useDynamicProxy = isDynamicProxy(proxyHost, proxyPort);
+        boolean useProxy = useStaticProxy || useDynamicProxy;
+
+        // if both dynamic and static are used, the dynamic proxy has priority over static
+        if(!useDynamicProxy) {
+            proxyScheme = PROXY_SCHEME;
+            proxyHost = PROXY_HOST;
+            proxyPort = PROXY_PORT;
+            proxyUser = PROXY_USER;
+            proxyPass = PROXY_PASS;
+        }
+
+        // Lookup key - must agree with all the values used to create the HttpClient.
+        return new HttpClientKey(url, useProxy, proxyScheme, proxyHost, proxyPort, proxyUser, proxyPass);
+    }
+
     /**
      * Reset SSL State. <br/>
      * In order to do that we need to:
@@ -1105,14 +1147,21 @@ public class HTTPHC4Impl extends HTTPHCA
      * </ul>
      * @param jMeterVariables {@link JMeterVariables}
      * @param clientContext {@link HttpClientContext}
-     * @param mapHttpClientPerHttpClientKey Map of {@link Pair} holding {@link CloseableHttpClient} and {@link PoolingHttpClientConnectionManager}
+     * @param mapHttpClientPerHttpClientKey Map of {@link MutableTriple} holding {@link CloseableHttpClient} and {@link PoolingHttpClientConnectionManager}
      */
-    private void resetStateIfNeeded(JMeterVariables jMeterVariables, 
+    private void resetStateIfNeeded(
+            MutableTriple<CloseableHttpClient, AuthState, PoolingHttpClientConnectionManager> triple,
+            JMeterVariables jMeterVariables, 
             HttpClientContext clientContext,
-            Map<HttpClientKey, Pair<CloseableHttpClient, PoolingHttpClientConnectionManager>> mapHttpClientPerHttpClientKey) {
+            Map<HttpClientKey, MutableTriple<CloseableHttpClient, AuthState, PoolingHttpClientConnectionManager>> mapHttpClientPerHttpClientKey) {
         if (resetStateOnThreadGroupIteration.get()) {
             closeCurrentConnections(mapHttpClientPerHttpClientKey);
             clientContext.removeAttribute(HttpClientContext.USER_TOKEN);
+            clientContext.removeAttribute(HttpClientContext.PROXY_AUTH_STATE);
+            if (triple != null) {
+                triple.setMiddle(null);
+            }
+            jMeterVariables.remove(JMETER_VARIABLE_USER_TOKEN);
             ((JsseSSLManager) SSLManager.getInstance()).resetContext();
             resetStateOnThreadGroupIteration.set(false);
         }
@@ -1122,10 +1171,10 @@ public class HTTPHC4Impl extends HTTPHCA
      * @param mapHttpClientPerHttpClientKey
      */
     private void closeCurrentConnections(
-            Map<HttpClientKey, Pair<CloseableHttpClient, PoolingHttpClientConnectionManager>> mapHttpClientPerHttpClientKey) {
-        for (Pair<CloseableHttpClient, PoolingHttpClientConnectionManager> pair :
+            Map<HttpClientKey, MutableTriple<CloseableHttpClient, AuthState, PoolingHttpClientConnectionManager>> mapHttpClientPerHttpClientKey) {
+        for (MutableTriple<CloseableHttpClient, AuthState, PoolingHttpClientConnectionManager> triple :
                 mapHttpClientPerHttpClientKey.values()) {
-            PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = pair.getRight();
+            PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = triple.getRight();
             poolingHttpClientConnectionManager.closeExpiredConnections();
             poolingHttpClientConnectionManager.closeIdleConnections(1L, TimeUnit.MICROSECONDS);
         }
@@ -1507,7 +1556,7 @@ public class HTTPHC4Impl extends HTTPHCA
                     // Allow the mimetype of the file to control the content type
                     // This is not obvious in GUI if you are not uploading any files,
                     // but just sending the content of nameless parameters
-                    // TODO: needs a multiple file upload scenerio
+                    // TODO: needs a multiple file upload scenario
                     if(!hasContentTypeHeader) {
                         HTTPFileArg file = files.length > 0? files[0] : null;
                         if(file != null && file.getMimeType() != null && file.getMimeType().length() > 0) {
@@ -1750,12 +1799,12 @@ public class HTTPHC4Impl extends HTTPHCA
      */
     private void closeThreadLocalConnections() {
         // Does not need to be synchronised, as all access is from same thread
-        Map<HttpClientKey, Pair<CloseableHttpClient, PoolingHttpClientConnectionManager>> 
+        Map<HttpClientKey, MutableTriple<CloseableHttpClient, AuthState, PoolingHttpClientConnectionManager>>
             mapHttpClientPerHttpClientKey = HTTPCLIENTS_CACHE_PER_THREAD_AND_HTTPCLIENTKEY.get();
-        if ( mapHttpClientPerHttpClientKey != null ) {
-            for ( Pair<CloseableHttpClient, PoolingHttpClientConnectionManager> pair : mapHttpClientPerHttpClientKey.values() ) {
-                JOrphanUtils.closeQuietly(pair.getLeft());
-                JOrphanUtils.closeQuietly(pair.getRight());
+        if (mapHttpClientPerHttpClientKey != null ) {
+            for (MutableTriple<CloseableHttpClient, AuthState, PoolingHttpClientConnectionManager> triple : mapHttpClientPerHttpClientKey.values() ) {
+                JOrphanUtils.closeQuietly(triple.getLeft());
+                JOrphanUtils.closeQuietly(triple.getRight());
             }
             mapHttpClientPerHttpClientKey.clear();
         }

Modified: jmeter/trunk/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/jmeter/trunk/xdocs/changes.xml?rev=1859826&r1=1859825&r2=1859826&view=diff
==============================================================================
--- jmeter/trunk/xdocs/changes.xml [utf-8] (original)
+++ jmeter/trunk/xdocs/changes.xml [utf-8] Thu May 23 21:26:12 2019
@@ -137,6 +137,7 @@ to view the last major behaviors with th
     <li><bug>63298</bug>HTTP Requests with encoded URLs are being sent in decoded format</li>
     <li><bug>63364</bug>When setting <code>subresults.disable_renaming=true</code>, sub results are still renamed using their parent SampleLabel while they shouldn't. Contributed by Ubik Load Pack (support at ubikloadpack.com)</li>
     <li><bug>63129</bug>JMeter can not identify encoding during first time page submission. Based partly on analysis and PR made by Naveen Nandwani (naveen.nandwani at india.nec.com)</li>
+    <li><bug>62672</bug>HTTP Request send double requests when configure proxy with authentication. Based on patch by Artem Fedorov (artem.fedorov at blazemeter.com) and contributed by BlazeMeter.</li>
 </ul>
 
 <h3>Other Samplers</h3>
@@ -191,6 +192,7 @@ to view the last major behaviors with th
   <li><a href="https://ubikloadpack.com">Ubik Load Pack</a></li>
   <li>Xia Li</li>
   <li>Naveen Nandwani (naveen.nandwani at india.nec.com)</li>
+  <li>Artem Fedorov (artem.fedorov at blazemeter.com)</li>
 </ul>
 <p>We also thank bug reporters who helped us improve JMeter.</p>
 <ul>