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 2021/09/26 09:31:42 UTC

[httpcomponents-client] branch master updated (76c98cd -> e843aa2)

This is an automated email from the ASF dual-hosted git repository.

olegk pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git.


 discard 76c98cd  Logger cleanup (no functional changes)
 discard 7e39101  Refactored AuthCache keeping logic into a separate utility class
     new 9555dbf  Refactored AuthCache keeping logic into a separate utility class
     new fdee794  Logger cleanup (no functional changes)
     new e843aa2  AuthCache conformance to RFC 7617

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (76c98cd)
            \
             N -- N -- N   refs/heads/master (e843aa2)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../AbstractHttpAsyncClientAuthentication.java     |  78 +++++++++
 .../testing/async/TestH2ClientAuthentication.java  |  12 ++
 .../async/TestHttp1ClientAuthentication.java       |  12 ++
 .../testing/sync/TestClientAuthentication.java     |  77 +++++++++
 .../apache/hc/client5/http/SchemePortResolver.java |  10 ++
 .../org/apache/hc/client5/http/auth/AuthCache.java |  61 +++++++
 .../http/impl/DefaultSchemePortResolver.java       |  14 +-
 .../{ProtocolSupport.java => RequestSupport.java}  |  59 +++----
 .../client5/http/impl/async/AsyncConnectExec.java  |   8 +-
 .../client5/http/impl/async/AsyncProtocolExec.java |  25 +--
 .../hc/client5/http/impl/auth/AuthCacheKeeper.java |  27 +--
 .../hc/client5/http/impl/auth/BasicAuthCache.java  |  96 +++++++++--
 .../client5/http/impl/auth/HttpAuthenticator.java  |   3 +
 .../hc/client5/http/impl/classic/ConnectExec.java  |   8 +-
 .../hc/client5/http/impl/classic/ProtocolExec.java |  20 ++-
 .../hc/client5/http/protocol/RequestAuthCache.java |  21 +--
 .../hc/client5/http/impl/TestProtocolSupport.java  |  55 ------
 .../hc/client5/http/impl/TestRequestSupport.java   |  55 ++++++
 .../client5/http/impl/auth/TestBasicAuthCache.java |   2 +-
 .../http/impl/auth/TestRequestAuthCache.java       | 188 ---------------------
 20 files changed, 495 insertions(+), 336 deletions(-)
 rename httpclient5/src/main/java/org/apache/hc/client5/http/impl/{ProtocolSupport.java => RequestSupport.java} (52%)
 delete mode 100644 httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestProtocolSupport.java
 create mode 100644 httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestRequestSupport.java
 delete mode 100644 httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestRequestAuthCache.java

[httpcomponents-client] 03/03: AuthCache conformance to RFC 7617

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git

commit e843aa26d5f1e581028c0df6f975820b0873a714
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sun Sep 26 11:21:13 2021 +0200

    AuthCache conformance to RFC 7617
---
 .../AbstractHttpAsyncClientAuthentication.java     |  78 +++++++++
 .../testing/async/TestH2ClientAuthentication.java  |  12 ++
 .../async/TestHttp1ClientAuthentication.java       |  12 ++
 .../testing/sync/TestClientAuthentication.java     |  77 +++++++++
 .../apache/hc/client5/http/SchemePortResolver.java |  10 ++
 .../org/apache/hc/client5/http/auth/AuthCache.java |  61 +++++++
 .../http/impl/DefaultSchemePortResolver.java       |  14 +-
 .../{ProtocolSupport.java => RequestSupport.java}  |  59 +++----
 .../client5/http/impl/async/AsyncConnectExec.java  |   8 +-
 .../client5/http/impl/async/AsyncProtocolExec.java |  25 +--
 .../hc/client5/http/impl/auth/AuthCacheKeeper.java |  27 +--
 .../hc/client5/http/impl/auth/BasicAuthCache.java  |  96 +++++++++--
 .../hc/client5/http/impl/classic/ConnectExec.java  |   8 +-
 .../hc/client5/http/impl/classic/ProtocolExec.java |  20 ++-
 .../hc/client5/http/impl/TestProtocolSupport.java  |  55 ------
 .../hc/client5/http/impl/TestRequestSupport.java   |  55 ++++++
 .../client5/http/impl/auth/TestBasicAuthCache.java |   2 +-
 .../http/impl/auth/TestRequestAuthCache.java       | 188 ---------------------
 18 files changed, 484 insertions(+), 323 deletions(-)

diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncClientAuthentication.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncClientAuthentication.java
index 88f73b2..5de4511 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncClientAuthentication.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncClientAuthentication.java
@@ -26,14 +26,19 @@
  */
 package org.apache.hc.client5.testing.async;
 
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
 
 import org.apache.hc.client5.http.AuthenticationStrategy;
 import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
 import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
 import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
+import org.apache.hc.client5.http.auth.AuthCache;
 import org.apache.hc.client5.http.auth.AuthSchemeFactory;
 import org.apache.hc.client5.http.auth.AuthScope;
 import org.apache.hc.client5.http.auth.CredentialsProvider;
@@ -42,6 +47,7 @@ import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
 import org.apache.hc.client5.http.config.RequestConfig;
 import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy;
 import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.auth.BasicAuthCache;
 import org.apache.hc.client5.http.impl.auth.BasicScheme;
 import org.apache.hc.client5.http.impl.auth.CredentialsProviderBuilder;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
@@ -52,7 +58,9 @@ import org.apache.hc.core5.http.ContentType;
 import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpRequestInterceptor;
 import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpResponseInterceptor;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.HttpVersion;
 import org.apache.hc.core5.http.URIScheme;
@@ -64,9 +72,12 @@ import org.apache.hc.core5.http.impl.HttpProcessors;
 import org.apache.hc.core5.http.message.BasicHeader;
 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
 import org.apache.hc.core5.http.protocol.HttpCoreContext;
+import org.apache.hc.core5.http.support.BasicResponseBuilder;
 import org.apache.hc.core5.http2.config.H2Config;
 import org.apache.hc.core5.http2.impl.H2Processors;
 import org.apache.hc.core5.net.URIAuthority;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
 import org.junit.Assert;
 import org.junit.Test;
 import org.mockito.Mockito;
@@ -104,6 +115,10 @@ public abstract class AbstractHttpAsyncClientAuthentication<T extends CloseableH
 
     abstract void setTargetAuthenticationStrategy(AuthenticationStrategy targetAuthStrategy);
 
+    abstract void addResponseInterceptor(HttpResponseInterceptor responseInterceptor);
+
+    abstract void addRequestInterceptor(final HttpRequestInterceptor requestInterceptor);
+
     @Test
     public void testBasicAuthenticationNoCreds() throws Exception {
         server.register("*", AsyncEchoHandler::new);
@@ -271,6 +286,69 @@ public abstract class AbstractHttpAsyncClientAuthentication<T extends CloseableH
     }
 
     @Test
+    public void testBasicAuthenticationCredentialsCachingByPathPrefix() throws Exception {
+        server.register("*", AsyncEchoHandler::new);
+
+        final DefaultAuthenticationStrategy authStrategy = Mockito.spy(new DefaultAuthenticationStrategy());
+        setTargetAuthenticationStrategy(authStrategy);
+        final Queue<HttpResponse> responseQueue = new ConcurrentLinkedQueue<>();
+        addResponseInterceptor((response, entity, context)
+                -> responseQueue.add(BasicResponseBuilder.copy(response).build()));
+
+        final HttpHost target = start();
+
+        final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
+                .add(target, "test", "test".toCharArray())
+                .build();
+
+        final AuthCache authCache = new BasicAuthCache();
+
+        for (final String requestPath: new String[] {"/blah/a", "/blah/b?huh", "/blah/c", "/bl%61h/%61"}) {
+            final HttpClientContext context = HttpClientContext.create();
+            context.setAuthCache(authCache);
+            context.setCredentialsProvider(credentialsProvider);
+            final Future<SimpleHttpResponse> future = httpclient.execute(SimpleRequestBuilder.get()
+                    .setHttpHost(target)
+                    .setPath(requestPath)
+                    .build(), context, null);
+            final HttpResponse response = future.get();
+            Assert.assertNotNull(response);
+            Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+        }
+
+        // There should be only single auth strategy call for all successful message exchanges
+        Mockito.verify(authStrategy).select(Mockito.any(), Mockito.any(), Mockito.any());
+
+        MatcherAssert.assertThat(
+                responseQueue.stream().map(HttpResponse::getCode).collect(Collectors.toList()),
+                CoreMatchers.equalTo(Arrays.asList(401, 200, 200, 200, 200)));
+
+        responseQueue.clear();
+        authCache.clear();
+        Mockito.reset(authStrategy);
+
+        for (final String requestPath: new String[] {"/blah/a", "/yada/a", "/blah/blah/"}) {
+            final HttpClientContext context = HttpClientContext.create();
+            context.setCredentialsProvider(credentialsProvider);
+            context.setAuthCache(authCache);
+            final Future<SimpleHttpResponse> future = httpclient.execute(SimpleRequestBuilder.get()
+                    .setHttpHost(target)
+                    .setPath(requestPath)
+                    .build(), context, null);
+            final HttpResponse response = future.get();
+            Assert.assertNotNull(response);
+            Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+        }
+
+        // There should be an auth strategy call for all successful message exchanges
+        Mockito.verify(authStrategy, Mockito.times(3)).select(Mockito.any(), Mockito.any(), Mockito.any());
+
+        MatcherAssert.assertThat(
+                responseQueue.stream().map(HttpResponse::getCode).collect(Collectors.toList()),
+                CoreMatchers.equalTo(Arrays.asList(401, 200, 401, 200, 401, 200)));
+    }
+
+    @Test
     public void testAuthenticationUserinfoInRequestSuccess() throws Exception {
         server.register("*", AsyncEchoHandler::new);
         final HttpHost target = start();
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestH2ClientAuthentication.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestH2ClientAuthentication.java
index 52adf5a..94b6766 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestH2ClientAuthentication.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestH2ClientAuthentication.java
@@ -38,6 +38,8 @@ import org.apache.hc.client5.http.impl.async.H2AsyncClientBuilder;
 import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
 import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
 import org.apache.hc.client5.testing.SSLTestContexts;
+import org.apache.hc.core5.http.HttpRequestInterceptor;
+import org.apache.hc.core5.http.HttpResponseInterceptor;
 import org.apache.hc.core5.http.HttpVersion;
 import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.config.Lookup;
@@ -93,6 +95,16 @@ public class TestH2ClientAuthentication extends AbstractHttpAsyncClientAuthentic
     }
 
     @Override
+    void addResponseInterceptor(final HttpResponseInterceptor responseInterceptor) {
+        clientBuilder.addResponseInterceptorLast(responseInterceptor);
+    }
+
+    @Override
+    void addRequestInterceptor(final HttpRequestInterceptor requestInterceptor) {
+        clientBuilder.addRequestInterceptorLast(requestInterceptor);
+    }
+
+    @Override
     protected CloseableHttpAsyncClient createClient() throws Exception {
         return clientBuilder.build();
     }
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1ClientAuthentication.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1ClientAuthentication.java
index d8ff129..1459237 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1ClientAuthentication.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1ClientAuthentication.java
@@ -51,7 +51,9 @@ import org.apache.hc.client5.testing.SSLTestContexts;
 import org.apache.hc.core5.http.HeaderElements;
 import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpRequestInterceptor;
 import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpResponseInterceptor;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.HttpVersion;
 import org.apache.hc.core5.http.URIScheme;
@@ -133,6 +135,16 @@ public class TestHttp1ClientAuthentication extends AbstractHttpAsyncClientAuthen
     }
 
     @Override
+    void addResponseInterceptor(final HttpResponseInterceptor responseInterceptor) {
+        clientBuilder.addResponseInterceptorLast(responseInterceptor);
+    }
+
+    @Override
+    void addRequestInterceptor(final HttpRequestInterceptor requestInterceptor) {
+        clientBuilder.addRequestInterceptorLast(requestInterceptor);
+    }
+
+    @Override
     protected CloseableHttpAsyncClient createClient() throws Exception {
         return clientBuilder.build();
     }
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestClientAuthentication.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestClientAuthentication.java
index 1e88a46..3334085 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestClientAuthentication.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestClientAuthentication.java
@@ -30,8 +30,12 @@ import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
 
 import org.apache.hc.client5.http.auth.AuthCache;
 import org.apache.hc.client5.http.auth.AuthScheme;
@@ -63,6 +67,7 @@ import org.apache.hc.core5.http.HttpEntity;
 import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpResponse;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.config.Registry;
 import org.apache.hc.core5.http.config.RegistryBuilder;
@@ -74,7 +79,10 @@ import org.apache.hc.core5.http.io.entity.StringEntity;
 import org.apache.hc.core5.http.message.BasicHeader;
 import org.apache.hc.core5.http.protocol.HttpContext;
 import org.apache.hc.core5.http.protocol.HttpCoreContext;
+import org.apache.hc.core5.http.support.BasicResponseBuilder;
 import org.apache.hc.core5.net.URIAuthority;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
 import org.junit.Assert;
 import org.junit.Test;
 import org.mockito.Mockito;
@@ -267,6 +275,9 @@ public class TestClientAuthentication extends LocalServerTestBase {
         this.server.registerHandler("*", new EchoHandler());
         final DefaultAuthenticationStrategy authStrategy = Mockito.spy(new DefaultAuthenticationStrategy());
         this.clientBuilder.setTargetAuthenticationStrategy(authStrategy);
+        final Queue<HttpResponse> responseQueue = new ConcurrentLinkedQueue<>();
+        this.clientBuilder.addResponseInterceptorLast((response, entity, context)
+                -> responseQueue.add(BasicResponseBuilder.copy(response).build()));
 
         final HttpHost target = start();
 
@@ -286,6 +297,72 @@ public class TestClientAuthentication extends LocalServerTestBase {
         }
 
         Mockito.verify(authStrategy).select(Mockito.any(), Mockito.any(), Mockito.any());
+
+        MatcherAssert.assertThat(
+                responseQueue.stream().map(HttpResponse::getCode).collect(Collectors.toList()),
+                CoreMatchers.equalTo(Arrays.asList(401, 200, 200, 200, 200, 200)));
+    }
+
+    @Test
+    public void testBasicAuthenticationCredentialsCachingByPathPrefix() throws Exception {
+        this.server.registerHandler("*", new EchoHandler());
+        final DefaultAuthenticationStrategy authStrategy = Mockito.spy(new DefaultAuthenticationStrategy());
+        this.clientBuilder.setTargetAuthenticationStrategy(authStrategy);
+        final Queue<HttpResponse> responseQueue = new ConcurrentLinkedQueue<>();
+        this.clientBuilder.addResponseInterceptorLast((response, entity, context)
+                -> responseQueue.add(BasicResponseBuilder.copy(response).build()));
+
+        final HttpHost target = start();
+
+        final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
+                .add(target, "test", "test".toCharArray())
+                .build();
+
+        final AuthCache authCache = new BasicAuthCache();
+
+        for (final String requestPath: new String[] {"/blah/a", "/blah/b?huh", "/blah/c", "/bl%61h/%61"}) {
+            final HttpClientContext context = HttpClientContext.create();
+            context.setAuthCache(authCache);
+            context.setCredentialsProvider(credentialsProvider);
+            final HttpGet httpget = new HttpGet(requestPath);
+            try (final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context)) {
+                final HttpEntity entity1 = response.getEntity();
+                Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+                Assert.assertNotNull(entity1);
+                EntityUtils.consume(entity1);
+            }
+        }
+
+        // There should be only single auth strategy call for all successful message exchanges
+        Mockito.verify(authStrategy).select(Mockito.any(), Mockito.any(), Mockito.any());
+
+        MatcherAssert.assertThat(
+                responseQueue.stream().map(HttpResponse::getCode).collect(Collectors.toList()),
+                CoreMatchers.equalTo(Arrays.asList(401, 200, 200, 200, 200)));
+
+        responseQueue.clear();
+        authCache.clear();
+        Mockito.reset(authStrategy);
+
+        for (final String requestPath: new String[] {"/blah/a", "/yada/a", "/blah/blah/"}) {
+            final HttpClientContext context = HttpClientContext.create();
+            context.setCredentialsProvider(credentialsProvider);
+            context.setAuthCache(authCache);
+            final HttpGet httpget = new HttpGet(requestPath);
+            try (final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context)) {
+                final HttpEntity entity1 = response.getEntity();
+                Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+                Assert.assertNotNull(entity1);
+                EntityUtils.consume(entity1);
+            }
+        }
+
+        // There should be an auth strategy call for all successful message exchanges
+        Mockito.verify(authStrategy, Mockito.times(3)).select(Mockito.any(), Mockito.any(), Mockito.any());
+
+        MatcherAssert.assertThat(
+                responseQueue.stream().map(HttpResponse::getCode).collect(Collectors.toList()),
+                CoreMatchers.equalTo(Arrays.asList(401, 200, 401, 200, 401, 200)));
     }
 
     @Test
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/SchemePortResolver.java b/httpclient5/src/main/java/org/apache/hc/client5/http/SchemePortResolver.java
index 26ddb42..f57e019 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/SchemePortResolver.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/SchemePortResolver.java
@@ -29,6 +29,7 @@ package org.apache.hc.client5.http;
 import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
 import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.net.NamedEndpoint;
 
 /**
  * Strategy for default port resolution for protocol schemes.
@@ -43,4 +44,13 @@ public interface SchemePortResolver {
      */
     int resolve(HttpHost host);
 
+    /**
+     * Returns the actual port for the host based on the protocol scheme.
+     *
+     * @since 5.2
+     */
+    default int resolve(String scheme, NamedEndpoint endpoint) {
+        return resolve(new HttpHost(scheme, endpoint));
+    }
+
 }
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/AuthCache.java b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/AuthCache.java
index f566b39..e13bde7 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/AuthCache.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/AuthCache.java
@@ -36,12 +36,73 @@ import org.apache.hc.core5.http.HttpHost;
  */
 public interface AuthCache {
 
+    /**
+     * Stores the authentication state with the given authentication scope in the cache.
+     *
+     * @param host the authentication authority.
+     * @param authScheme the cacheable authentication state.
+     */
     void put(HttpHost host, AuthScheme authScheme);
 
+    /**
+     * Returns the authentication state with the given authentication scope from the cache
+     * if available.
+     *
+     * @param host the authentication authority.
+     * @return the authentication state ir {@code null} if not available in the cache.
+     */
     AuthScheme get(HttpHost host);
 
+    /**
+     * Removes the authentication state with the given authentication scope from the cache
+     * if found.
+     *
+     * @param host the authentication authority.
+     */
     void remove(HttpHost host);
 
     void clear();
 
+    /**
+     * Stores the authentication state with the given authentication scope in the cache.
+     *
+     * @param host the authentication authority.
+     * @param pathPrefix the path prefix (the path component up to the last segment separator).
+     *                   Can be {@code null}.
+     * @param authScheme the cacheable authentication state.
+     *
+     * @since 5.2
+     */
+    default void put(HttpHost host, String pathPrefix, AuthScheme authScheme) {
+        put(host, authScheme);
+    }
+
+    /**
+     * Returns the authentication state with the given authentication scope from the cache
+     * if available.
+     * @param host the authentication authority.
+     * @param pathPrefix the path prefix (the path component up to the last segment separator).
+     *                   Can be {@code null}.
+     * @return the authentication state ir {@code null} if not available in the cache.
+     *
+     * @since 5.2
+     */
+    default AuthScheme get(HttpHost host, String pathPrefix) {
+        return get(host);
+    }
+
+    /**
+     * Removes the authentication state with the given authentication scope from the cache
+     * if found.
+     *
+     * @param host the authentication authority.
+     * @param pathPrefix the path prefix (the path component up to the last segment separator).
+     *                   Can be {@code null}.
+     *
+     * @since 5.2
+     */
+    default void remove(HttpHost host, String pathPrefix) {
+        remove(host);
+    }
+
 }
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultSchemePortResolver.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultSchemePortResolver.java
index 144ae5f..c65e65d 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultSchemePortResolver.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultSchemePortResolver.java
@@ -31,6 +31,7 @@ import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
 import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.net.NamedEndpoint;
 import org.apache.hc.core5.util.Args;
 
 /**
@@ -46,14 +47,19 @@ public class DefaultSchemePortResolver implements SchemePortResolver {
     @Override
     public int resolve(final HttpHost host) {
         Args.notNull(host, "HTTP host");
-        final int port = host.getPort();
+        return resolve(host.getSchemeName(), host);
+    }
+
+    @Override
+    public int resolve(final String scheme, final NamedEndpoint endpoint) {
+        Args.notNull(endpoint, "Endpoint");
+        final int port = endpoint.getPort();
         if (port > 0) {
             return port;
         }
-        final String name = host.getSchemeName();
-        if (URIScheme.HTTP.same(name)) {
+        if (URIScheme.HTTP.same(scheme)) {
             return 80;
-        } else if (URIScheme.HTTPS.same(name)) {
+        } else if (URIScheme.HTTPS.same(scheme)) {
             return 443;
         } else {
             return -1;
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/ProtocolSupport.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/RequestSupport.java
similarity index 52%
rename from httpclient5/src/main/java/org/apache/hc/client5/http/impl/ProtocolSupport.java
rename to httpclient5/src/main/java/org/apache/hc/client5/http/impl/RequestSupport.java
index 70f820c..9e7dee1 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/ProtocolSupport.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/RequestSupport.java
@@ -26,45 +26,46 @@
  */
 package org.apache.hc.client5.http.impl;
 
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
 import org.apache.hc.core5.annotation.Internal;
 import org.apache.hc.core5.http.HttpRequest;
-import org.apache.hc.core5.http.URIScheme;
-import org.apache.hc.core5.net.URIAuthority;
+import org.apache.hc.core5.net.PercentCodec;
+import org.apache.hc.core5.net.URIBuilder;
 
 /**
- * Protocol support methods.
+ * Protocol support methods. For internal use only.
  *
- * @since 5.1
+ * @since 5.2
  */
 @Internal
-public final class ProtocolSupport {
+public final class RequestSupport {
 
-    public static String getRequestUri(final HttpRequest request) {
-        final URIAuthority authority = request.getAuthority();
-        if (authority != null) {
-            final StringBuilder buf = new StringBuilder();
-            final String scheme = request.getScheme();
-            buf.append(scheme != null ? scheme : URIScheme.HTTP.id);
-            buf.append("://");
-            if (authority.getUserInfo() != null) {
-                buf.append(authority.getUserInfo());
-                buf.append("@");
-            }
-            buf.append(authority.getHostName());
-            if (authority.getPort() != -1) {
-                buf.append(":");
-                buf.append(authority.getPort());
-            }
-            final String path = request.getPath();
-            if (path == null || !path.startsWith("/")) {
-                buf.append("/");
+    public static String extractPathPrefix(final HttpRequest request) {
+        try {
+            final URIBuilder uriBuilder = new URIBuilder(request.getPath());
+            uriBuilder.setFragment(null);
+            uriBuilder.clearParameters();
+            uriBuilder.normalizeSyntax();
+            final List<String> pathSegments = uriBuilder.getPathSegments();
+
+            if (!pathSegments.isEmpty()) {
+                pathSegments.remove(pathSegments.size() - 1);
             }
-            if (path != null) {
-                buf.append(path);
+            if (pathSegments.isEmpty()) {
+                return null;
+            } else {
+                final StringBuilder buf = new StringBuilder();
+                for (final String pathSegment : pathSegments) {
+                    buf.append('/');
+                    PercentCodec.encode(buf, pathSegment, StandardCharsets.US_ASCII);
+                }
+                return buf.toString();
             }
-            return buf.toString();
-        } else {
-            return request.getPath();
+        } catch (final URISyntaxException ex) {
+            return null;
         }
     }
 
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncConnectExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncConnectExec.java
index 4f83962..17d75c6 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncConnectExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncConnectExec.java
@@ -379,7 +379,7 @@ public final class AsyncConnectExec implements AsyncExecChainHandler {
         final AuthExchange proxyAuthExchange = proxy != null ? clientContext.getAuthExchange(proxy) : new AuthExchange();
 
         if (authCacheKeeper != null) {
-            authCacheKeeper.loadPreemptively(proxy, proxyAuthExchange, clientContext);
+            authCacheKeeper.loadPreemptively(proxy, null, proxyAuthExchange, clientContext);
         }
 
         final HttpRequest connect = new BasicHttpRequest(Method.CONNECT, nextHop, nextHop.toHostString());
@@ -444,9 +444,9 @@ public final class AsyncConnectExec implements AsyncExecChainHandler {
 
             if (authCacheKeeper != null) {
                 if (proxyAuthRequested) {
-                    authCacheKeeper.updateOnChallenge(proxy, proxyAuthExchange, context);
+                    authCacheKeeper.updateOnChallenge(proxy, null, proxyAuthExchange, context);
                 } else {
-                    authCacheKeeper.updateOnNoChallenge(proxy, proxyAuthExchange, context);
+                    authCacheKeeper.updateOnNoChallenge(proxy, null, proxyAuthExchange, context);
                 }
             }
 
@@ -455,7 +455,7 @@ public final class AsyncConnectExec implements AsyncExecChainHandler {
                         proxyAuthStrategy, proxyAuthExchange, context);
 
                 if (authCacheKeeper != null) {
-                    authCacheKeeper.updateOnResponse(proxy, proxyAuthExchange, context);
+                    authCacheKeeper.updateOnResponse(proxy, null, proxyAuthExchange, context);
                 }
 
                 return updated;
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java
index 4fe3637..14dd3ce 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java
@@ -43,6 +43,7 @@ import org.apache.hc.client5.http.auth.CredentialsProvider;
 import org.apache.hc.client5.http.auth.CredentialsStore;
 import org.apache.hc.client5.http.config.RequestConfig;
 import org.apache.hc.client5.http.impl.AuthSupport;
+import org.apache.hc.client5.http.impl.RequestSupport;
 import org.apache.hc.client5.http.impl.auth.AuthCacheKeeper;
 import org.apache.hc.client5.http.impl.auth.HttpAuthenticator;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
@@ -148,23 +149,25 @@ public final class AsyncProtocolExec implements AsyncExecChainHandler {
         }
 
         final HttpHost target = new HttpHost(request.getScheme(), request.getAuthority());
+        final String pathPrefix = RequestSupport.extractPathPrefix(request);
         final AuthExchange targetAuthExchange = clientContext.getAuthExchange(target);
         final AuthExchange proxyAuthExchange = proxy != null ? clientContext.getAuthExchange(proxy) : new AuthExchange();
 
         if (authCacheKeeper != null) {
-            authCacheKeeper.loadPreemptively(target, targetAuthExchange, clientContext);
+            authCacheKeeper.loadPreemptively(target, pathPrefix, targetAuthExchange, clientContext);
             if (proxy != null) {
-                authCacheKeeper.loadPreemptively(proxy, proxyAuthExchange, clientContext);
+                authCacheKeeper.loadPreemptively(proxy, null, proxyAuthExchange, clientContext);
             }
         }
 
         final AtomicBoolean challenged = new AtomicBoolean(false);
-        internalExecute(target, targetAuthExchange, proxyAuthExchange,
+        internalExecute(target, pathPrefix, targetAuthExchange, proxyAuthExchange,
                 challenged, request, entityProducer, scope, chain, asyncExecCallback);
     }
 
     private void internalExecute(
             final HttpHost target,
+            final String pathPrefix,
             final AuthExchange targetAuthExchange,
             final AuthExchange proxyAuthExchange,
             final AtomicBoolean challenged,
@@ -216,6 +219,7 @@ public final class AsyncProtocolExec implements AsyncExecChainHandler {
                         proxyAuthExchange,
                         proxy != null ? proxy : target,
                         target,
+                        pathPrefix,
                         response,
                         clientContext)) {
                     challenged.set(true);
@@ -267,7 +271,7 @@ public final class AsyncProtocolExec implements AsyncExecChainHandler {
                             if (entityProducer != null) {
                                 entityProducer.releaseResources();
                             }
-                            internalExecute(target, targetAuthExchange, proxyAuthExchange,
+                            internalExecute(target, pathPrefix, targetAuthExchange, proxyAuthExchange,
                                     challenged, request, entityProducer, scope, chain, asyncExecCallback);
                         } catch (final HttpException | IOException ex) {
                             asyncExecCallback.failed(ex);
@@ -298,6 +302,7 @@ public final class AsyncProtocolExec implements AsyncExecChainHandler {
             final AuthExchange proxyAuthExchange,
             final HttpHost proxy,
             final HttpHost target,
+            final String pathPrefix,
             final HttpResponse response,
             final HttpClientContext context) {
         final RequestConfig config = context.getRequestConfig();
@@ -307,9 +312,9 @@ public final class AsyncProtocolExec implements AsyncExecChainHandler {
 
             if (authCacheKeeper != null) {
                 if (targetAuthRequested) {
-                    authCacheKeeper.updateOnChallenge(target, targetAuthExchange, context);
+                    authCacheKeeper.updateOnChallenge(target, pathPrefix, targetAuthExchange, context);
                 } else {
-                    authCacheKeeper.updateOnNoChallenge(target, targetAuthExchange, context);
+                    authCacheKeeper.updateOnNoChallenge(target, pathPrefix, targetAuthExchange, context);
                 }
             }
 
@@ -318,9 +323,9 @@ public final class AsyncProtocolExec implements AsyncExecChainHandler {
 
             if (authCacheKeeper != null) {
                 if (proxyAuthRequested) {
-                    authCacheKeeper.updateOnChallenge(proxy, proxyAuthExchange, context);
+                    authCacheKeeper.updateOnChallenge(proxy, null, proxyAuthExchange, context);
                 } else {
-                    authCacheKeeper.updateOnNoChallenge(proxy, proxyAuthExchange, context);
+                    authCacheKeeper.updateOnNoChallenge(proxy, null, proxyAuthExchange, context);
                 }
             }
 
@@ -329,7 +334,7 @@ public final class AsyncProtocolExec implements AsyncExecChainHandler {
                         targetAuthStrategy, targetAuthExchange, context);
 
                 if (authCacheKeeper != null) {
-                    authCacheKeeper.updateOnResponse(target, targetAuthExchange, context);
+                    authCacheKeeper.updateOnResponse(target, pathPrefix, targetAuthExchange, context);
                 }
 
                 return updated;
@@ -339,7 +344,7 @@ public final class AsyncProtocolExec implements AsyncExecChainHandler {
                         proxyAuthStrategy, proxyAuthExchange, context);
 
                 if (authCacheKeeper != null) {
-                    authCacheKeeper.updateOnResponse(proxy, proxyAuthExchange, context);
+                    authCacheKeeper.updateOnResponse(proxy, null, proxyAuthExchange, context);
                 }
 
                 return updated;
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/AuthCacheKeeper.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/AuthCacheKeeper.java
index 01f959a..985a23f 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/AuthCacheKeeper.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/AuthCacheKeeper.java
@@ -59,32 +59,36 @@ public final class AuthCacheKeeper {
     }
 
     public void updateOnChallenge(final HttpHost host,
+                                  final String pathPrefix,
                                   final AuthExchange authExchange,
                                   final HttpContext context) {
-        clearCache(host, HttpClientContext.adapt(context));
+        clearCache(host, pathPrefix, HttpClientContext.adapt(context));
     }
 
     public void updateOnNoChallenge(final HttpHost host,
+                                    final String pathPrefix,
                                     final AuthExchange authExchange,
                                     final HttpContext context) {
         if (authExchange.getState() == AuthExchange.State.SUCCESS) {
-            updateCache(host, authExchange.getAuthScheme(), HttpClientContext.adapt(context));
+            updateCache(host, pathPrefix, authExchange.getAuthScheme(), HttpClientContext.adapt(context));
         }
     }
 
     public void updateOnResponse(final HttpHost host,
+                                 final String pathPrefix,
                                  final AuthExchange authExchange,
                                  final HttpContext context) {
         if (authExchange.getState() == AuthExchange.State.FAILURE) {
-            clearCache(host, HttpClientContext.adapt(context));
+            clearCache(host, pathPrefix, HttpClientContext.adapt(context));
         }
     }
 
     public void loadPreemptively(final HttpHost host,
+                                 final String pathPrefix,
                                  final AuthExchange authExchange,
                                  final HttpContext context) {
         if (authExchange.getState() == AuthExchange.State.UNCHALLENGED) {
-            final AuthScheme authScheme = loadFromCache(host, HttpClientContext.adapt(context));
+            final AuthScheme authScheme = loadFromCache(host, pathPrefix, HttpClientContext.adapt(context));
             if (authScheme != null) {
                 authExchange.select(authScheme);
             }
@@ -92,14 +96,15 @@ public final class AuthCacheKeeper {
     }
 
     private AuthScheme loadFromCache(final HttpHost host,
+                                     final String pathPrefix,
                                      final HttpClientContext clientContext) {
         final AuthCache authCache = clientContext.getAuthCache();
         if (authCache != null) {
-            final AuthScheme authScheme = authCache.get(host);
+            final AuthScheme authScheme = authCache.get(host, pathPrefix);
             if (authScheme != null) {
                 if (LOG.isDebugEnabled()) {
                     final String exchangeId = clientContext.getExchangeId();
-                    LOG.debug("{} Re-using cached '{}' auth scheme for {}", exchangeId, authScheme.getName(), host);
+                    LOG.debug("{} Re-using cached '{}' auth scheme for {} {}", exchangeId, authScheme.getName(), host, pathPrefix);
                 }
                 return authScheme;
             }
@@ -108,6 +113,7 @@ public final class AuthCacheKeeper {
     }
 
     private void updateCache(final HttpHost host,
+                             final String pathPrefix,
                              final AuthScheme authScheme,
                              final HttpClientContext clientContext) {
         final boolean cacheable = authScheme.getClass().getAnnotation(AuthStateCacheable.class) != null;
@@ -119,21 +125,22 @@ public final class AuthCacheKeeper {
             }
             if (LOG.isDebugEnabled()) {
                 final String exchangeId = clientContext.getExchangeId();
-                LOG.debug("{} Caching '{}' auth scheme for {}", exchangeId, authScheme.getName(), host);
+                LOG.debug("{} Caching '{}' auth scheme for {} {}", exchangeId, authScheme.getName(), host, pathPrefix);
             }
-            authCache.put(host, authScheme);
+            authCache.put(host, pathPrefix, authScheme);
         }
     }
 
     private void clearCache(final HttpHost host,
+                            final String pathPrefix,
                             final HttpClientContext clientContext) {
         final AuthCache authCache = clientContext.getAuthCache();
         if (authCache != null) {
             if (LOG.isDebugEnabled()) {
                 final String exchangeId = clientContext.getExchangeId();
-                LOG.debug("{} Clearing cached auth scheme for {}", exchangeId, host);
+                LOG.debug("{} Clearing cached auth scheme for {} {}", exchangeId, host, pathPrefix);
             }
-            authCache.remove(host);
+            authCache.remove(host, pathPrefix);
         }
     }
 
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicAuthCache.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicAuthCache.java
index c22c57b..c0c92c7 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicAuthCache.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicAuthCache.java
@@ -32,6 +32,7 @@ import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
+import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -39,11 +40,12 @@ import org.apache.hc.client5.http.SchemePortResolver;
 import org.apache.hc.client5.http.auth.AuthCache;
 import org.apache.hc.client5.http.auth.AuthScheme;
 import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
-import org.apache.hc.client5.http.routing.RoutingSupport;
 import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
 import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.net.NamedEndpoint;
 import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.LangUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -62,7 +64,65 @@ public class BasicAuthCache implements AuthCache {
 
     private static final Logger LOG = LoggerFactory.getLogger(BasicAuthCache.class);
 
-    private final Map<HttpHost, byte[]> map;
+    static class Key {
+
+        final String scheme;
+        final String host;
+        final int port;
+        final String pathPrefix;
+
+        Key(final String scheme, final String host, final int port, final String pathPrefix) {
+            Args.notBlank(scheme, "Scheme");
+            Args.notBlank(host, "Scheme");
+            this.scheme = scheme.toLowerCase(Locale.ROOT);
+            this.host = host.toLowerCase(Locale.ROOT);
+            this.port = port;
+            this.pathPrefix = pathPrefix;
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof Key) {
+                final Key that = (Key) obj;
+                return this.scheme.equals(that.scheme) &&
+                        this.host.equals(that.host) &&
+                        this.port == that.port &&
+                        LangUtils.equals(this.pathPrefix, that.pathPrefix);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            int hash = LangUtils.HASH_SEED;
+            hash = LangUtils.hashCode(hash, this.scheme);
+            hash = LangUtils.hashCode(hash, this.host);
+            hash = LangUtils.hashCode(hash, this.port);
+            hash = LangUtils.hashCode(hash, this.pathPrefix);
+            return hash;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder buf = new StringBuilder();
+            buf.append(scheme).append("://").append(host);
+            if (port >= 0) {
+                buf.append(":").append(port);
+            }
+            if (pathPrefix != null) {
+                if (!pathPrefix.startsWith("/")) {
+                    buf.append("/");
+                }
+                buf.append(pathPrefix);
+            }
+            return buf.toString();
+        }
+    }
+
+    private final Map<Key, byte[]> map;
     private final SchemePortResolver schemePortResolver;
 
     /**
@@ -80,8 +140,27 @@ public class BasicAuthCache implements AuthCache {
         this(null);
     }
 
+    private Key key(final String scheme, final NamedEndpoint authority, final String pathPrefix) {
+        return new Key(scheme, authority.getHostName(), schemePortResolver.resolve(scheme, authority), pathPrefix);
+    }
+
     @Override
     public void put(final HttpHost host, final AuthScheme authScheme) {
+        put(host, null, authScheme);
+    }
+
+    @Override
+    public AuthScheme get(final HttpHost host) {
+        return get(host, null);
+    }
+
+    @Override
+    public void remove(final HttpHost host) {
+        remove(host, null);
+    }
+
+    @Override
+    public void put(final HttpHost host, final String pathPrefix, final AuthScheme authScheme) {
         Args.notNull(host, "HTTP host");
         if (authScheme == null) {
             return;
@@ -92,8 +171,7 @@ public class BasicAuthCache implements AuthCache {
                 try (final ObjectOutputStream out = new ObjectOutputStream(buf)) {
                     out.writeObject(authScheme);
                 }
-                final HttpHost key = RoutingSupport.normalize(host, schemePortResolver);
-                this.map.put(key, buf.toByteArray());
+                this.map.put(key(host.getSchemeName(), host, pathPrefix), buf.toByteArray());
             } catch (final IOException ex) {
                 if (LOG.isWarnEnabled()) {
                     LOG.warn("Unexpected I/O error while serializing auth scheme", ex);
@@ -107,10 +185,9 @@ public class BasicAuthCache implements AuthCache {
     }
 
     @Override
-    public AuthScheme get(final HttpHost host) {
+    public AuthScheme get(final HttpHost host, final String pathPrefix) {
         Args.notNull(host, "HTTP host");
-        final HttpHost key = RoutingSupport.normalize(host, schemePortResolver);
-        final byte[] bytes = this.map.get(key);
+        final byte[] bytes = this.map.get(key(host.getSchemeName(), host, pathPrefix));
         if (bytes != null) {
             try {
                 final ByteArrayInputStream buf = new ByteArrayInputStream(bytes);
@@ -131,10 +208,9 @@ public class BasicAuthCache implements AuthCache {
     }
 
     @Override
-    public void remove(final HttpHost host) {
+    public void remove(final HttpHost host, final String pathPrefix) {
         Args.notNull(host, "HTTP host");
-        final HttpHost key = RoutingSupport.normalize(host, schemePortResolver);
-        this.map.remove(key);
+        this.map.remove(key(host.getSchemeName(), host, pathPrefix));
     }
 
     @Override
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ConnectExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ConnectExec.java
index 5b80e21..45c0c5e 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ConnectExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ConnectExec.java
@@ -215,7 +215,7 @@ public final class ConnectExec implements ExecChainHandler {
         final AuthExchange proxyAuthExchange = context.getAuthExchange(proxy);
 
         if (authCacheKeeper != null) {
-            authCacheKeeper.loadPreemptively(proxy, proxyAuthExchange, context);
+            authCacheKeeper.loadPreemptively(proxy, null, proxyAuthExchange, context);
         }
 
         ClassicHttpResponse response = null;
@@ -254,9 +254,9 @@ public final class ConnectExec implements ExecChainHandler {
 
                 if (authCacheKeeper != null) {
                     if (proxyAuthRequested) {
-                        authCacheKeeper.updateOnChallenge(proxy, proxyAuthExchange, context);
+                        authCacheKeeper.updateOnChallenge(proxy, null, proxyAuthExchange, context);
                     } else {
-                        authCacheKeeper.updateOnNoChallenge(proxy, proxyAuthExchange, context);
+                        authCacheKeeper.updateOnNoChallenge(proxy, null, proxyAuthExchange, context);
                     }
                 }
 
@@ -265,7 +265,7 @@ public final class ConnectExec implements ExecChainHandler {
                             proxyAuthStrategy, proxyAuthExchange, context);
 
                     if (authCacheKeeper != null) {
-                        authCacheKeeper.updateOnResponse(proxy, proxyAuthExchange, context);
+                        authCacheKeeper.updateOnResponse(proxy, null, proxyAuthExchange, context);
                     }
                     if (updated) {
                         // Retry request
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProtocolExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProtocolExec.java
index 556f7a6..77dc575 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProtocolExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProtocolExec.java
@@ -42,6 +42,7 @@ import org.apache.hc.client5.http.classic.ExecChainHandler;
 import org.apache.hc.client5.http.classic.ExecRuntime;
 import org.apache.hc.client5.http.config.RequestConfig;
 import org.apache.hc.client5.http.impl.AuthSupport;
+import org.apache.hc.client5.http.impl.RequestSupport;
 import org.apache.hc.client5.http.impl.auth.AuthCacheKeeper;
 import org.apache.hc.client5.http.impl.auth.HttpAuthenticator;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
@@ -151,14 +152,15 @@ public final class ProtocolExec implements ExecChainHandler {
             }
 
             final HttpHost target = new HttpHost(request.getScheme(), request.getAuthority());
+            final String pathPrefix = RequestSupport.extractPathPrefix(request);
 
             final AuthExchange targetAuthExchange = context.getAuthExchange(target);
             final AuthExchange proxyAuthExchange = proxy != null ? context.getAuthExchange(proxy) : new AuthExchange();
 
             if (authCacheKeeper != null) {
-                authCacheKeeper.loadPreemptively(target, targetAuthExchange, context);
+                authCacheKeeper.loadPreemptively(target, pathPrefix, targetAuthExchange, context);
                 if (proxy != null) {
-                    authCacheKeeper.loadPreemptively(proxy, proxyAuthExchange, context);
+                    authCacheKeeper.loadPreemptively(proxy, null, proxyAuthExchange, context);
                 }
             }
 
@@ -206,6 +208,7 @@ public final class ProtocolExec implements ExecChainHandler {
                         proxyAuthExchange,
                         proxy != null ? proxy : target,
                         target,
+                        pathPrefix,
                         response,
                         context)) {
                     // Make sure the response body is fully consumed, if present
@@ -259,6 +262,7 @@ public final class ProtocolExec implements ExecChainHandler {
             final AuthExchange proxyAuthExchange,
             final HttpHost proxy,
             final HttpHost target,
+            final String pathPrefix,
             final HttpResponse response,
             final HttpClientContext context) {
         final RequestConfig config = context.getRequestConfig();
@@ -268,9 +272,9 @@ public final class ProtocolExec implements ExecChainHandler {
 
             if (authCacheKeeper != null) {
                 if (targetAuthRequested) {
-                    authCacheKeeper.updateOnChallenge(target, targetAuthExchange, context);
+                    authCacheKeeper.updateOnChallenge(target, pathPrefix, targetAuthExchange, context);
                 } else {
-                    authCacheKeeper.updateOnNoChallenge(target, targetAuthExchange, context);
+                    authCacheKeeper.updateOnNoChallenge(target, pathPrefix, targetAuthExchange, context);
                 }
             }
 
@@ -279,9 +283,9 @@ public final class ProtocolExec implements ExecChainHandler {
 
             if (authCacheKeeper != null) {
                 if (proxyAuthRequested) {
-                    authCacheKeeper.updateOnChallenge(proxy, proxyAuthExchange, context);
+                    authCacheKeeper.updateOnChallenge(proxy, null, proxyAuthExchange, context);
                 } else {
-                    authCacheKeeper.updateOnNoChallenge(proxy, proxyAuthExchange, context);
+                    authCacheKeeper.updateOnNoChallenge(proxy, null, proxyAuthExchange, context);
                 }
             }
 
@@ -290,7 +294,7 @@ public final class ProtocolExec implements ExecChainHandler {
                         targetAuthStrategy, targetAuthExchange, context);
 
                 if (authCacheKeeper != null) {
-                    authCacheKeeper.updateOnResponse(target, targetAuthExchange, context);
+                    authCacheKeeper.updateOnResponse(target, pathPrefix, targetAuthExchange, context);
                 }
 
                 return updated;
@@ -300,7 +304,7 @@ public final class ProtocolExec implements ExecChainHandler {
                         proxyAuthStrategy, proxyAuthExchange, context);
 
                 if (authCacheKeeper != null) {
-                    authCacheKeeper.updateOnResponse(proxy, proxyAuthExchange, context);
+                    authCacheKeeper.updateOnResponse(proxy, null, proxyAuthExchange, context);
                 }
 
                 return updated;
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestProtocolSupport.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestProtocolSupport.java
deleted file mode 100644
index 0af8432..0000000
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestProtocolSupport.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * ====================================================================
- * 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.hc.client5.http.impl;
-
-import org.apache.hc.core5.http.HttpRequest;
-import org.apache.hc.core5.http.Method;
-import org.apache.hc.core5.http.message.BasicHttpRequest;
-import org.apache.hc.core5.net.URIAuthority;
-import org.hamcrest.CoreMatchers;
-import org.hamcrest.MatcherAssert;
-import org.junit.Test;
-
-/**
- * Simple tests for {@link ProtocolSupport}.
- */
-public class TestProtocolSupport {
-
-    @Test
-    public void testGetRequestUri() {
-        final HttpRequest request = new BasicHttpRequest(Method.GET, "");
-        MatcherAssert.assertThat(ProtocolSupport.getRequestUri(request), CoreMatchers.equalTo("/"));
-        request.setAuthority(new URIAuthority("testUser", "localhost", 8080));
-        MatcherAssert.assertThat(ProtocolSupport.getRequestUri(request), CoreMatchers.equalTo("http://testUser@localhost:8080/"));
-        request.setScheme("https");
-        MatcherAssert.assertThat(ProtocolSupport.getRequestUri(request), CoreMatchers.equalTo("https://testUser@localhost:8080/"));
-        request.setPath("blah");
-        MatcherAssert.assertThat(ProtocolSupport.getRequestUri(request), CoreMatchers.equalTo("https://testUser@localhost:8080/blah"));
-        request.setPath("/blah/blah");
-        MatcherAssert.assertThat(ProtocolSupport.getRequestUri(request), CoreMatchers.equalTo("https://testUser@localhost:8080/blah/blah"));
-    }
-}
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestRequestSupport.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestRequestSupport.java
new file mode 100644
index 0000000..80abef5
--- /dev/null
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestRequestSupport.java
@@ -0,0 +1,55 @@
+/*
+ * ====================================================================
+ * 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.hc.client5.http.impl;
+
+import org.apache.hc.core5.http.message.BasicHttpRequest;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Simple tests for {@link RequestSupport}.
+ *
+ * @since 5.2
+ */
+public class TestRequestSupport {
+
+    @Test
+    public void testPathPrefixExtraction() {
+        Assert.assertEquals("/aaaa", RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/aaaa/bbbb")));
+        Assert.assertEquals("/aaaa", RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/aaaa/")));
+        Assert.assertEquals("/aaaa", RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/aaaa/../aaaa/")));
+        Assert.assertEquals("/aaaa/bbbb", RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/aaaa/bbbb/cccc")));
+        Assert.assertEquals("/aaaa/bbbb", RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/aaaa/bbbb/")));
+        Assert.assertEquals("/aaaa", RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/aaaa/bbbb?////")));
+        Assert.assertEquals("/aa%2Faa", RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/aa%2faa/bbbb")));
+        Assert.assertEquals("/aa%2Faa", RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/a%61%2fa%61/bbbb")));
+        Assert.assertNull(RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/")));
+        Assert.assertNull(RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/aaaa")));
+        Assert.assertNull(RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "")));
+    }
+
+}
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestBasicAuthCache.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestBasicAuthCache.java
index b57fa6d..3486065 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestBasicAuthCache.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestBasicAuthCache.java
@@ -67,7 +67,7 @@ public class TestBasicAuthCache {
     }
 
     @Test
-    public void testStoreNonserializable() throws Exception {
+    public void testStoreNonSerializable() throws Exception {
         final BasicAuthCache cache = new BasicAuthCache();
         final AuthScheme authScheme = new NTLMScheme();
         cache.put(new HttpHost("localhost", 80), authScheme);
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestRequestAuthCache.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestRequestAuthCache.java
deleted file mode 100644
index d6959c1..0000000
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestRequestAuthCache.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * ====================================================================
- * 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.hc.client5.http.impl.auth;
-
-import org.apache.hc.client5.http.HttpRoute;
-import org.apache.hc.client5.http.auth.AuthCache;
-import org.apache.hc.client5.http.auth.AuthExchange;
-import org.apache.hc.client5.http.auth.AuthScope;
-import org.apache.hc.client5.http.auth.Credentials;
-import org.apache.hc.client5.http.auth.CredentialsProvider;
-import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
-import org.apache.hc.client5.http.protocol.HttpClientContext;
-import org.apache.hc.client5.http.protocol.RequestAuthCache;
-import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.HttpRequest;
-import org.apache.hc.core5.http.HttpRequestInterceptor;
-import org.apache.hc.core5.http.message.BasicHttpRequest;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-public class TestRequestAuthCache {
-
-    private HttpHost target;
-    private HttpHost proxy;
-    private Credentials creds1;
-    private Credentials creds2;
-    private AuthScope authscope1;
-    private AuthScope authscope2;
-    private BasicScheme authscheme1;
-    private BasicScheme authscheme2;
-    private CredentialsProvider credProvider;
-
-    @Before
-    public void setUp() {
-        this.target = new HttpHost("localhost", 80);
-        this.proxy = new HttpHost("localhost", 8080);
-
-        this.creds1 = new UsernamePasswordCredentials("user1", "secret1".toCharArray());
-        this.creds2 = new UsernamePasswordCredentials("user2", "secret2".toCharArray());
-        this.authscope1 = new AuthScope(this.target);
-        this.authscope2 = new AuthScope(this.proxy);
-        this.authscheme1 = new BasicScheme();
-        this.authscheme2 = new BasicScheme();
-
-        this.credProvider = CredentialsProviderBuilder.create()
-                .add(this.authscope1, this.creds1)
-                .add(this.authscope2, this.creds2)
-                .build();
-    }
-
-    @Test
-    public void testRequestParameterCheck() throws Exception {
-        final HttpClientContext context = HttpClientContext.create();
-        final HttpRequestInterceptor interceptor = new RequestAuthCache();
-        Assert.assertThrows(NullPointerException.class, () ->
-                interceptor.process(null, null, context));
-    }
-
-    @Test
-    public void testContextParameterCheck() throws Exception {
-        final HttpRequest request = new BasicHttpRequest("GET", "/");
-        final HttpRequestInterceptor interceptor = new RequestAuthCache();
-        Assert.assertThrows(NullPointerException.class, () ->
-                interceptor.process(request, null, null));
-    }
-
-    @Test
-    public void testPreemptiveTargetAndProxyAuth() throws Exception {
-        final HttpRequest request = new BasicHttpRequest("GET", "/");
-
-        final HttpClientContext context = HttpClientContext.create();
-        context.setAttribute(HttpClientContext.CREDS_PROVIDER, this.credProvider);
-        context.setAttribute(HttpClientContext.HTTP_ROUTE, new HttpRoute(this.target, null, this.proxy, false));
-
-        final AuthCache authCache = new BasicAuthCache();
-        authCache.put(this.target, this.authscheme1);
-        authCache.put(this.proxy, this.authscheme2);
-
-        context.setAttribute(HttpClientContext.AUTH_CACHE, authCache);
-
-        final HttpRequestInterceptor interceptor = new RequestAuthCache();
-        interceptor.process(request, null, context);
-
-        final AuthExchange targetAuthExchange = context.getAuthExchange(this.target);
-        final AuthExchange proxyAuthExchange = context.getAuthExchange(this.proxy);
-
-        Assert.assertNotNull(targetAuthExchange);
-        Assert.assertNotNull(targetAuthExchange.getAuthScheme());
-        Assert.assertNotNull(proxyAuthExchange);
-        Assert.assertNotNull(proxyAuthExchange.getAuthScheme());
-    }
-
-    @Test
-    public void testCredentialsProviderNotSet() throws Exception {
-        final HttpRequest request = new BasicHttpRequest("GET", "/");
-
-        final HttpClientContext context = HttpClientContext.create();
-        context.setAttribute(HttpClientContext.CREDS_PROVIDER, null);
-        context.setAttribute(HttpClientContext.HTTP_ROUTE, new HttpRoute(this.target, null, this.proxy, false));
-
-        final AuthCache authCache = new BasicAuthCache();
-        authCache.put(this.target, this.authscheme1);
-        authCache.put(this.proxy, this.authscheme2);
-
-        context.setAttribute(HttpClientContext.AUTH_CACHE, authCache);
-
-        final HttpRequestInterceptor interceptor = new RequestAuthCache();
-        interceptor.process(request, null, context);
-
-        final AuthExchange targetAuthExchange = context.getAuthExchange(this.target);
-        final AuthExchange proxyAuthExchange = context.getAuthExchange(this.proxy);
-
-        Assert.assertNotNull(targetAuthExchange);
-        Assert.assertNull(targetAuthExchange.getAuthScheme());
-        Assert.assertNotNull(proxyAuthExchange);
-        Assert.assertNull(proxyAuthExchange.getAuthScheme());
-    }
-
-    @Test
-    public void testAuthCacheNotSet() throws Exception {
-        final HttpRequest request = new BasicHttpRequest("GET", "/");
-
-        final HttpClientContext context = HttpClientContext.create();
-        context.setAttribute(HttpClientContext.CREDS_PROVIDER, this.credProvider);
-        context.setAttribute(HttpClientContext.HTTP_ROUTE, new HttpRoute(this.target, null, this.proxy, false));
-        context.setAttribute(HttpClientContext.AUTH_CACHE, null);
-
-        final HttpRequestInterceptor interceptor = new RequestAuthCache();
-        interceptor.process(request, null, context);
-
-        final AuthExchange targetAuthExchange = context.getAuthExchange(this.target);
-        final AuthExchange proxyAuthExchange = context.getAuthExchange(this.proxy);
-
-        Assert.assertNotNull(targetAuthExchange);
-        Assert.assertNull(targetAuthExchange.getAuthScheme());
-        Assert.assertNotNull(proxyAuthExchange);
-        Assert.assertNull(proxyAuthExchange.getAuthScheme());
-    }
-
-    @Test
-    public void testAuthCacheEmpty() throws Exception {
-        final HttpRequest request = new BasicHttpRequest("GET", "/");
-
-        final HttpClientContext context = HttpClientContext.create();
-        context.setAttribute(HttpClientContext.CREDS_PROVIDER, this.credProvider);
-        context.setAttribute(HttpClientContext.HTTP_ROUTE, new HttpRoute(this.target, null, this.proxy, false));
-
-        final AuthCache authCache = new BasicAuthCache();
-        context.setAttribute(HttpClientContext.AUTH_CACHE, authCache);
-
-        final HttpRequestInterceptor interceptor = new RequestAuthCache();
-        interceptor.process(request, null, context);
-
-        final AuthExchange targetAuthExchange = context.getAuthExchange(this.target);
-        final AuthExchange proxyAuthExchange = context.getAuthExchange(this.proxy);
-
-        Assert.assertNotNull(targetAuthExchange);
-        Assert.assertNull(targetAuthExchange.getAuthScheme());
-        Assert.assertNotNull(proxyAuthExchange);
-        Assert.assertNull(proxyAuthExchange.getAuthScheme());
-    }
-
-}

[httpcomponents-client] 02/03: Logger cleanup (no functional changes)

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git

commit fdee794ee86378f6e5b925376355f3f57078f278
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sat Sep 25 18:55:46 2021 +0200

    Logger cleanup (no functional changes)
---
 .../client5/http/impl/auth/HttpAuthenticator.java  | 64 +++++++++++-----------
 1 file changed, 31 insertions(+), 33 deletions(-)

diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/HttpAuthenticator.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/HttpAuthenticator.java
index 0a5592b..a5507fd 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/HttpAuthenticator.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/HttpAuthenticator.java
@@ -72,13 +72,11 @@ import org.slf4j.LoggerFactory;
 @Contract(threading = ThreadingBehavior.STATELESS)
 public final class HttpAuthenticator {
 
-    private static final Logger DEFAULT_LOGGER = LoggerFactory.getLogger(HttpAuthenticator.class);
+    private static final Logger LOG = LoggerFactory.getLogger(HttpAuthenticator.class);
 
-    private final Logger log;
     private final AuthChallengeParser parser;
 
     public HttpAuthenticator() {
-        this.log = DEFAULT_LOGGER;
         this.parser = new AuthChallengeParser();
     }
 
@@ -115,16 +113,16 @@ public final class HttpAuthenticator {
         final String exchangeId = clientContext.getExchangeId();
 
         if (response.getCode() == challengeCode) {
-            if (log.isDebugEnabled()) {
-                log.debug("{} Authentication required", exchangeId);
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("{} Authentication required", exchangeId);
             }
             return true;
         }
         switch (authExchange.getState()) {
         case CHALLENGED:
         case HANDSHAKE:
-            if (log.isDebugEnabled()) {
-                log.debug("{} Authentication succeeded", exchangeId);
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("{} Authentication succeeded", exchangeId);
             }
             authExchange.setState(AuthExchange.State.SUCCESS);
             break;
@@ -160,8 +158,8 @@ public final class HttpAuthenticator {
         final HttpClientContext clientContext = HttpClientContext.adapt(context);
         final String exchangeId = clientContext.getExchangeId();
 
-        if (log.isDebugEnabled()) {
-            log.debug("{} {} requested authentication", exchangeId, host.toHostString());
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("{} {} requested authentication", exchangeId, host.toHostString());
         }
 
         final Header[] headers = response.getHeaders(
@@ -187,8 +185,8 @@ public final class HttpAuthenticator {
             try {
                 authChallenges = parser.parse(challengeType, buffer, cursor);
             } catch (final ParseException ex) {
-                if (log.isWarnEnabled()) {
-                    log.warn("{} Malformed challenge: {}", exchangeId, header.getValue());
+                if (LOG.isWarnEnabled()) {
+                    LOG.warn("{} Malformed challenge: {}", exchangeId, header.getValue());
                 }
                 continue;
             }
@@ -200,8 +198,8 @@ public final class HttpAuthenticator {
             }
         }
         if (challengeMap.isEmpty()) {
-            if (log.isDebugEnabled()) {
-                log.debug("{} Response contains no valid authentication challenges", exchangeId);
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("{} Response contains no valid authentication challenges", exchangeId);
             }
             authExchange.reset();
             return false;
@@ -222,22 +220,22 @@ public final class HttpAuthenticator {
                     final String schemeName = authScheme.getName();
                     final AuthChallenge challenge = challengeMap.get(schemeName.toLowerCase(Locale.ROOT));
                     if (challenge != null) {
-                        if (log.isDebugEnabled()) {
-                            log.debug("{} Authorization challenge processed", exchangeId);
+                        if (LOG.isDebugEnabled()) {
+                            LOG.debug("{} Authorization challenge processed", exchangeId);
                         }
                         try {
                             authScheme.processChallenge(challenge, context);
                         } catch (final MalformedChallengeException ex) {
-                            if (log.isWarnEnabled()) {
-                                log.warn("{} {}", exchangeId, ex.getMessage());
+                            if (LOG.isWarnEnabled()) {
+                                LOG.warn("{} {}", exchangeId, ex.getMessage());
                             }
                             authExchange.reset();
                             authExchange.setState(AuthExchange.State.FAILURE);
                             return false;
                         }
                         if (authScheme.isChallengeComplete()) {
-                            if (log.isDebugEnabled()) {
-                                log.debug("{} Authentication failed", exchangeId);
+                            if (LOG.isDebugEnabled()) {
+                                LOG.debug("{} Authentication failed", exchangeId);
                             }
                             authExchange.reset();
                             authExchange.setState(AuthExchange.State.FAILURE);
@@ -254,15 +252,15 @@ public final class HttpAuthenticator {
         final List<AuthScheme> preferredSchemes = authStrategy.select(challengeType, challengeMap, context);
         final CredentialsProvider credsProvider = clientContext.getCredentialsProvider();
         if (credsProvider == null) {
-            if (log.isDebugEnabled()) {
-                log.debug("{} Credentials provider not set in the context", exchangeId);
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("{} Credentials provider not set in the context", exchangeId);
             }
             return false;
         }
 
         final Queue<AuthScheme> authOptions = new LinkedList<>();
-        if (log.isDebugEnabled()) {
-            log.debug("{} Selecting authentication options", exchangeId);
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("{} Selecting authentication options", exchangeId);
         }
         for (final AuthScheme authScheme: preferredSchemes) {
             try {
@@ -273,14 +271,14 @@ public final class HttpAuthenticator {
                     authOptions.add(authScheme);
                 }
             } catch (final AuthenticationException | MalformedChallengeException ex) {
-                if (log.isWarnEnabled()) {
-                    log.warn(ex.getMessage());
+                if (LOG.isWarnEnabled()) {
+                    LOG.warn(ex.getMessage());
                 }
             }
         }
         if (!authOptions.isEmpty()) {
-            if (log.isDebugEnabled()) {
-                log.debug("{} Selected authentication options: {}", exchangeId, authOptions);
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("{} Selected authentication options: {}", exchangeId, authOptions);
             }
             authExchange.reset();
             authExchange.setState(AuthExchange.State.CHALLENGED);
@@ -327,8 +325,8 @@ public final class HttpAuthenticator {
                 while (!authOptions.isEmpty()) {
                     authScheme = authOptions.remove();
                     authExchange.select(authScheme);
-                    if (log.isDebugEnabled()) {
-                        log.debug("{} Generating response to an authentication challenge using {} scheme",
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug("{} Generating response to an authentication challenge using {} scheme",
                                 exchangeId, authScheme.getName());
                     }
                     try {
@@ -339,8 +337,8 @@ public final class HttpAuthenticator {
                         request.addHeader(header);
                         break;
                     } catch (final AuthenticationException ex) {
-                        if (log.isWarnEnabled()) {
-                            log.warn("{} {} authentication error: {}", exchangeId, authScheme, ex.getMessage());
+                        if (LOG.isWarnEnabled()) {
+                            LOG.warn("{} {} authentication error: {}", exchangeId, authScheme, ex.getMessage());
                         }
                     }
                 }
@@ -357,8 +355,8 @@ public final class HttpAuthenticator {
                         authResponse);
                 request.addHeader(header);
             } catch (final AuthenticationException ex) {
-                if (log.isErrorEnabled()) {
-                    log.error("{} {} authentication error: {}", exchangeId, authScheme, ex.getMessage());
+                if (LOG.isErrorEnabled()) {
+                    LOG.error("{} {} authentication error: {}", exchangeId, authScheme, ex.getMessage());
                 }
             }
         }

[httpcomponents-client] 01/03: Refactored AuthCache keeping logic into a separate utility class

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git

commit 9555dbf40d6d30c728355e00b85c22efbe9adf31
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sat Sep 25 18:52:51 2021 +0200

    Refactored AuthCache keeping logic into a separate utility class
---
 .../client5/http/impl/async/AsyncConnectExec.java  |  33 ++++-
 .../client5/http/impl/async/AsyncProtocolExec.java |  82 +++++++++---
 .../http/impl/async/H2AsyncClientBuilder.java      |  15 ++-
 .../http/impl/async/HttpAsyncClientBuilder.java    |  15 ++-
 .../hc/client5/http/impl/auth/AuthCacheKeeper.java | 140 +++++++++++++++++++++
 .../client5/http/impl/auth/HttpAuthenticator.java  |  52 +-------
 .../hc/client5/http/impl/classic/ConnectExec.java  |  43 +++++--
 .../http/impl/classic/HttpClientBuilder.java       |  15 ++-
 .../hc/client5/http/impl/classic/ProtocolExec.java |  63 ++++++++--
 .../hc/client5/http/protocol/RequestAuthCache.java |  21 ++--
 .../http/impl/auth/TestHttpAuthenticator.java      |  13 --
 .../client5/http/impl/classic/TestConnectExec.java |   2 +-
 .../http/impl/classic/TestProtocolExec.java        |   2 +-
 13 files changed, 362 insertions(+), 134 deletions(-)

diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncConnectExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncConnectExec.java
index b0288c7..4f83962 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncConnectExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncConnectExec.java
@@ -33,6 +33,7 @@ import java.io.InterruptedIOException;
 import org.apache.hc.client5.http.AuthenticationStrategy;
 import org.apache.hc.client5.http.HttpRoute;
 import org.apache.hc.client5.http.RouteTracker;
+import org.apache.hc.client5.http.SchemePortResolver;
 import org.apache.hc.client5.http.async.AsyncExecCallback;
 import org.apache.hc.client5.http.async.AsyncExecChain;
 import org.apache.hc.client5.http.async.AsyncExecChainHandler;
@@ -41,6 +42,7 @@ import org.apache.hc.client5.http.auth.AuthExchange;
 import org.apache.hc.client5.http.auth.ChallengeType;
 import org.apache.hc.client5.http.config.RequestConfig;
 import org.apache.hc.client5.http.impl.TunnelRefusedException;
+import org.apache.hc.client5.http.impl.auth.AuthCacheKeeper;
 import org.apache.hc.client5.http.impl.auth.HttpAuthenticator;
 import org.apache.hc.client5.http.impl.routing.BasicRouteDirector;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
@@ -84,17 +86,21 @@ public final class AsyncConnectExec implements AsyncExecChainHandler {
     private final HttpProcessor proxyHttpProcessor;
     private final AuthenticationStrategy proxyAuthStrategy;
     private final HttpAuthenticator authenticator;
+    private final AuthCacheKeeper authCacheKeeper;
     private final HttpRouteDirector routeDirector;
 
     public AsyncConnectExec(
             final HttpProcessor proxyHttpProcessor,
-            final AuthenticationStrategy proxyAuthStrategy) {
+            final AuthenticationStrategy proxyAuthStrategy,
+            final SchemePortResolver schemePortResolver,
+            final boolean authCachingDisabled) {
         Args.notNull(proxyHttpProcessor, "Proxy HTTP processor");
         Args.notNull(proxyAuthStrategy, "Proxy authentication strategy");
         this.proxyHttpProcessor = proxyHttpProcessor;
         this.proxyAuthStrategy  = proxyAuthStrategy;
-        this.authenticator      = new HttpAuthenticator(LOG);
-        this.routeDirector      = new BasicRouteDirector();
+        this.authenticator = new HttpAuthenticator();
+        this.authCacheKeeper = authCachingDisabled ? null : new AuthCacheKeeper(schemePortResolver);
+        this.routeDirector = new BasicRouteDirector();
     }
 
     static class State {
@@ -372,6 +378,10 @@ public final class AsyncConnectExec implements AsyncExecChainHandler {
 
         final AuthExchange proxyAuthExchange = proxy != null ? clientContext.getAuthExchange(proxy) : new AuthExchange();
 
+        if (authCacheKeeper != null) {
+            authCacheKeeper.loadPreemptively(proxy, proxyAuthExchange, clientContext);
+        }
+
         final HttpRequest connect = new BasicHttpRequest(Method.CONNECT, nextHop, nextHop.toHostString());
         connect.setVersion(HttpVersion.HTTP_1_1);
 
@@ -431,9 +441,24 @@ public final class AsyncConnectExec implements AsyncExecChainHandler {
         final RequestConfig config = context.getRequestConfig();
         if (config.isAuthenticationEnabled()) {
             final boolean proxyAuthRequested = authenticator.isChallenged(proxy, ChallengeType.PROXY, response, proxyAuthExchange, context);
+
+            if (authCacheKeeper != null) {
+                if (proxyAuthRequested) {
+                    authCacheKeeper.updateOnChallenge(proxy, proxyAuthExchange, context);
+                } else {
+                    authCacheKeeper.updateOnNoChallenge(proxy, proxyAuthExchange, context);
+                }
+            }
+
             if (proxyAuthRequested) {
-                return authenticator.updateAuthState(proxy, ChallengeType.PROXY, response,
+                final boolean updated = authenticator.updateAuthState(proxy, ChallengeType.PROXY, response,
                         proxyAuthStrategy, proxyAuthExchange, context);
+
+                if (authCacheKeeper != null) {
+                    authCacheKeeper.updateOnResponse(proxy, proxyAuthExchange, context);
+                }
+
+                return updated;
             }
         }
         return false;
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java
index 880d9e5..4fe3637 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java
@@ -32,6 +32,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.hc.client5.http.AuthenticationStrategy;
 import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.SchemePortResolver;
 import org.apache.hc.client5.http.async.AsyncExecCallback;
 import org.apache.hc.client5.http.async.AsyncExecChain;
 import org.apache.hc.client5.http.async.AsyncExecChainHandler;
@@ -42,6 +43,7 @@ import org.apache.hc.client5.http.auth.CredentialsProvider;
 import org.apache.hc.client5.http.auth.CredentialsStore;
 import org.apache.hc.client5.http.config.RequestConfig;
 import org.apache.hc.client5.http.impl.AuthSupport;
+import org.apache.hc.client5.http.impl.auth.AuthCacheKeeper;
 import org.apache.hc.client5.http.impl.auth.HttpAuthenticator;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.core5.annotation.Contract;
@@ -87,15 +89,19 @@ public final class AsyncProtocolExec implements AsyncExecChainHandler {
     private final AuthenticationStrategy targetAuthStrategy;
     private final AuthenticationStrategy proxyAuthStrategy;
     private final HttpAuthenticator authenticator;
+    private final AuthCacheKeeper authCacheKeeper;
 
     AsyncProtocolExec(
             final HttpProcessor httpProcessor,
             final AuthenticationStrategy targetAuthStrategy,
-            final AuthenticationStrategy proxyAuthStrategy) {
+            final AuthenticationStrategy proxyAuthStrategy,
+            final SchemePortResolver schemePortResolver,
+            final boolean authCachingDisabled) {
         this.httpProcessor = Args.notNull(httpProcessor, "HTTP protocol processor");
         this.targetAuthStrategy = Args.notNull(targetAuthStrategy, "Target authentication strategy");
         this.proxyAuthStrategy = Args.notNull(proxyAuthStrategy, "Proxy authentication strategy");
-        this.authenticator = new HttpAuthenticator(LOG);
+        this.authenticator = new HttpAuthenticator();
+        this.authCacheKeeper = authCachingDisabled ? null : new AuthCacheKeeper(schemePortResolver);
     }
 
     @Override
@@ -141,11 +147,26 @@ public final class AsyncProtocolExec implements AsyncExecChainHandler {
             AuthSupport.extractFromAuthority(request.getScheme(), authority, (CredentialsStore) credsProvider);
         }
 
+        final HttpHost target = new HttpHost(request.getScheme(), request.getAuthority());
+        final AuthExchange targetAuthExchange = clientContext.getAuthExchange(target);
+        final AuthExchange proxyAuthExchange = proxy != null ? clientContext.getAuthExchange(proxy) : new AuthExchange();
+
+        if (authCacheKeeper != null) {
+            authCacheKeeper.loadPreemptively(target, targetAuthExchange, clientContext);
+            if (proxy != null) {
+                authCacheKeeper.loadPreemptively(proxy, proxyAuthExchange, clientContext);
+            }
+        }
+
         final AtomicBoolean challenged = new AtomicBoolean(false);
-        internalExecute(challenged, request, entityProducer, scope, chain, asyncExecCallback);
+        internalExecute(target, targetAuthExchange, proxyAuthExchange,
+                challenged, request, entityProducer, scope, chain, asyncExecCallback);
     }
 
     private void internalExecute(
+            final HttpHost target,
+            final AuthExchange targetAuthExchange,
+            final AuthExchange proxyAuthExchange,
             final AtomicBoolean challenged,
             final HttpRequest request,
             final AsyncEntityProducer entityProducer,
@@ -158,10 +179,6 @@ public final class AsyncProtocolExec implements AsyncExecChainHandler {
         final AsyncExecRuntime execRuntime = scope.execRuntime;
 
         final HttpHost proxy = route.getProxyHost();
-        final HttpHost target = new HttpHost(request.getScheme(), request.getAuthority());
-
-        final AuthExchange targetAuthExchange = clientContext.getAuthExchange(target);
-        final AuthExchange proxyAuthExchange = proxy != null ? clientContext.getAuthExchange(proxy) : new AuthExchange();
 
         clientContext.setAttribute(HttpClientContext.HTTP_ROUTE, route);
         clientContext.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
@@ -194,7 +211,13 @@ public final class AsyncProtocolExec implements AsyncExecChainHandler {
                     // Do not perform authentication for TRACE request
                     return asyncExecCallback.handleResponse(response, entityDetails);
                 }
-                if (needAuthentication(targetAuthExchange, proxyAuthExchange, route, request, response, clientContext)) {
+                if (needAuthentication(
+                        targetAuthExchange,
+                        proxyAuthExchange,
+                        proxy != null ? proxy : target,
+                        target,
+                        response,
+                        clientContext)) {
                     challenged.set(true);
                     return null;
                 }
@@ -244,7 +267,8 @@ public final class AsyncProtocolExec implements AsyncExecChainHandler {
                             if (entityProducer != null) {
                                 entityProducer.releaseResources();
                             }
-                            internalExecute(challenged, request, entityProducer, scope, chain, asyncExecCallback);
+                            internalExecute(target, targetAuthExchange, proxyAuthExchange,
+                                    challenged, request, entityProducer, scope, chain, asyncExecCallback);
                         } catch (final HttpException | IOException ex) {
                             asyncExecCallback.failed(ex);
                         }
@@ -272,31 +296,53 @@ public final class AsyncProtocolExec implements AsyncExecChainHandler {
     private boolean needAuthentication(
             final AuthExchange targetAuthExchange,
             final AuthExchange proxyAuthExchange,
-            final HttpRoute route,
-            final HttpRequest request,
+            final HttpHost proxy,
+            final HttpHost target,
             final HttpResponse response,
             final HttpClientContext context) {
         final RequestConfig config = context.getRequestConfig();
         if (config.isAuthenticationEnabled()) {
-            final HttpHost target = AuthSupport.resolveAuthTarget(request, route);
             final boolean targetAuthRequested = authenticator.isChallenged(
                     target, ChallengeType.TARGET, response, targetAuthExchange, context);
 
-            HttpHost proxy = route.getProxyHost();
-            // if proxy is not set use target host instead
-            if (proxy == null) {
-                proxy = route.getTargetHost();
+            if (authCacheKeeper != null) {
+                if (targetAuthRequested) {
+                    authCacheKeeper.updateOnChallenge(target, targetAuthExchange, context);
+                } else {
+                    authCacheKeeper.updateOnNoChallenge(target, targetAuthExchange, context);
+                }
             }
+
             final boolean proxyAuthRequested = authenticator.isChallenged(
                     proxy, ChallengeType.PROXY, response, proxyAuthExchange, context);
 
+            if (authCacheKeeper != null) {
+                if (proxyAuthRequested) {
+                    authCacheKeeper.updateOnChallenge(proxy, proxyAuthExchange, context);
+                } else {
+                    authCacheKeeper.updateOnNoChallenge(proxy, proxyAuthExchange, context);
+                }
+            }
+
             if (targetAuthRequested) {
-                return authenticator.updateAuthState(target, ChallengeType.TARGET, response,
+                final boolean updated = authenticator.updateAuthState(target, ChallengeType.TARGET, response,
                         targetAuthStrategy, targetAuthExchange, context);
+
+                if (authCacheKeeper != null) {
+                    authCacheKeeper.updateOnResponse(target, targetAuthExchange, context);
+                }
+
+                return updated;
             }
             if (proxyAuthRequested) {
-                return authenticator.updateAuthState(proxy, ChallengeType.PROXY, response,
+                final boolean updated = authenticator.updateAuthState(proxy, ChallengeType.PROXY, response,
                         proxyAuthStrategy, proxyAuthExchange, context);
+
+                if (authCacheKeeper != null) {
+                    authCacheKeeper.updateOnResponse(proxy, proxyAuthExchange, context);
+                }
+
+                return updated;
             }
         }
         return false;
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/H2AsyncClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/H2AsyncClientBuilder.java
index 494e925..098acdd 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/H2AsyncClientBuilder.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/H2AsyncClientBuilder.java
@@ -67,7 +67,6 @@ import org.apache.hc.client5.http.impl.nio.MultihomeConnectionInitiator;
 import org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner;
 import org.apache.hc.client5.http.protocol.RedirectStrategy;
 import org.apache.hc.client5.http.protocol.RequestAddCookies;
-import org.apache.hc.client5.http.protocol.RequestAuthCache;
 import org.apache.hc.client5.http.protocol.RequestDefaultHeaders;
 import org.apache.hc.client5.http.protocol.RequestExpectContinue;
 import org.apache.hc.client5.http.protocol.ResponseProcessCookies;
@@ -638,7 +637,9 @@ public class H2AsyncClientBuilder {
         execChainDefinition.addFirst(
                 new AsyncConnectExec(
                         new DefaultHttpProcessor(new RequestTargetHost(), new RequestUserAgent(userAgentCopy)),
-                        proxyAuthStrategyCopy),
+                        proxyAuthStrategyCopy,
+                        schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE,
+                        authCachingDisabled),
                 ChainElement.CONNECT.name());
 
         final HttpProcessorBuilder b = HttpProcessorBuilder.create();
@@ -663,9 +664,6 @@ public class H2AsyncClientBuilder {
         if (!cookieManagementDisabled) {
             b.add(new RequestAddCookies());
         }
-        if (!authCachingDisabled) {
-            b.add(new RequestAuthCache());
-        }
         if (!cookieManagementDisabled) {
             b.add(new ResponseProcessCookies());
         }
@@ -686,7 +684,12 @@ public class H2AsyncClientBuilder {
 
         final HttpProcessor httpProcessor = b.build();
         execChainDefinition.addFirst(
-                new AsyncProtocolExec(httpProcessor, targetAuthStrategyCopy, proxyAuthStrategyCopy),
+                new AsyncProtocolExec(
+                        httpProcessor,
+                        targetAuthStrategyCopy,
+                        proxyAuthStrategyCopy,
+                        schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE,
+                        authCachingDisabled),
                 ChainElement.PROTOCOL.name());
 
         // Add request retry executor, if not disabled
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
index 44a6759..6127ce0 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
@@ -75,7 +75,6 @@ import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner;
 import org.apache.hc.client5.http.nio.AsyncClientConnectionManager;
 import org.apache.hc.client5.http.protocol.RedirectStrategy;
 import org.apache.hc.client5.http.protocol.RequestAddCookies;
-import org.apache.hc.client5.http.protocol.RequestAuthCache;
 import org.apache.hc.client5.http.protocol.RequestDefaultHeaders;
 import org.apache.hc.client5.http.protocol.RequestExpectContinue;
 import org.apache.hc.client5.http.protocol.ResponseProcessCookies;
@@ -778,7 +777,9 @@ public class HttpAsyncClientBuilder {
         execChainDefinition.addFirst(
                 new AsyncConnectExec(
                         new DefaultHttpProcessor(new RequestTargetHost(), new RequestUserAgent(userAgentCopy)),
-                        proxyAuthStrategyCopy),
+                        proxyAuthStrategyCopy,
+                        schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE,
+                        authCachingDisabled),
                 ChainElement.CONNECT.name());
 
         final HttpProcessorBuilder b = HttpProcessorBuilder.create();
@@ -803,9 +804,6 @@ public class HttpAsyncClientBuilder {
         if (!cookieManagementDisabled) {
             b.add(new RequestAddCookies());
         }
-        if (!authCachingDisabled) {
-            b.add(new RequestAuthCache());
-        }
         if (!cookieManagementDisabled) {
             b.add(new ResponseProcessCookies());
         }
@@ -826,7 +824,12 @@ public class HttpAsyncClientBuilder {
 
         final HttpProcessor httpProcessor = b.build();
         execChainDefinition.addFirst(
-                new AsyncProtocolExec(httpProcessor, targetAuthStrategyCopy, proxyAuthStrategyCopy),
+                new AsyncProtocolExec(
+                        httpProcessor,
+                        targetAuthStrategyCopy,
+                        proxyAuthStrategyCopy,
+                        schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE,
+                        authCachingDisabled),
                 ChainElement.PROTOCOL.name());
 
         // Add request retry executor, if not disabled
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/AuthCacheKeeper.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/AuthCacheKeeper.java
new file mode 100644
index 0000000..01f959a
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/AuthCacheKeeper.java
@@ -0,0 +1,140 @@
+/*
+ * ====================================================================
+ * 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.hc.client5.http.impl.auth;
+
+import org.apache.hc.client5.http.SchemePortResolver;
+import org.apache.hc.client5.http.auth.AuthCache;
+import org.apache.hc.client5.http.auth.AuthExchange;
+import org.apache.hc.client5.http.auth.AuthScheme;
+import org.apache.hc.client5.http.auth.AuthStateCacheable;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.Internal;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility class that implements commons aspects of the client side authentication cache keeping.
+ *
+ * @since 5.2
+ */
+@Internal
+@Contract(threading = ThreadingBehavior.STATELESS)
+public final class AuthCacheKeeper {
+
+    private static final Logger LOG = LoggerFactory.getLogger(AuthCacheKeeper.class);
+
+    private final SchemePortResolver schemePortResolver;
+
+    public AuthCacheKeeper(final SchemePortResolver schemePortResolver) {
+        this.schemePortResolver = schemePortResolver;
+    }
+
+    public void updateOnChallenge(final HttpHost host,
+                                  final AuthExchange authExchange,
+                                  final HttpContext context) {
+        clearCache(host, HttpClientContext.adapt(context));
+    }
+
+    public void updateOnNoChallenge(final HttpHost host,
+                                    final AuthExchange authExchange,
+                                    final HttpContext context) {
+        if (authExchange.getState() == AuthExchange.State.SUCCESS) {
+            updateCache(host, authExchange.getAuthScheme(), HttpClientContext.adapt(context));
+        }
+    }
+
+    public void updateOnResponse(final HttpHost host,
+                                 final AuthExchange authExchange,
+                                 final HttpContext context) {
+        if (authExchange.getState() == AuthExchange.State.FAILURE) {
+            clearCache(host, HttpClientContext.adapt(context));
+        }
+    }
+
+    public void loadPreemptively(final HttpHost host,
+                                 final AuthExchange authExchange,
+                                 final HttpContext context) {
+        if (authExchange.getState() == AuthExchange.State.UNCHALLENGED) {
+            final AuthScheme authScheme = loadFromCache(host, HttpClientContext.adapt(context));
+            if (authScheme != null) {
+                authExchange.select(authScheme);
+            }
+        }
+    }
+
+    private AuthScheme loadFromCache(final HttpHost host,
+                                     final HttpClientContext clientContext) {
+        final AuthCache authCache = clientContext.getAuthCache();
+        if (authCache != null) {
+            final AuthScheme authScheme = authCache.get(host);
+            if (authScheme != null) {
+                if (LOG.isDebugEnabled()) {
+                    final String exchangeId = clientContext.getExchangeId();
+                    LOG.debug("{} Re-using cached '{}' auth scheme for {}", exchangeId, authScheme.getName(), host);
+                }
+                return authScheme;
+            }
+        }
+        return null;
+    }
+
+    private void updateCache(final HttpHost host,
+                             final AuthScheme authScheme,
+                             final HttpClientContext clientContext) {
+        final boolean cacheable = authScheme.getClass().getAnnotation(AuthStateCacheable.class) != null;
+        if (cacheable) {
+            AuthCache authCache = clientContext.getAuthCache();
+            if (authCache == null) {
+                authCache = new BasicAuthCache(schemePortResolver);
+                clientContext.setAuthCache(authCache);
+            }
+            if (LOG.isDebugEnabled()) {
+                final String exchangeId = clientContext.getExchangeId();
+                LOG.debug("{} Caching '{}' auth scheme for {}", exchangeId, authScheme.getName(), host);
+            }
+            authCache.put(host, authScheme);
+        }
+    }
+
+    private void clearCache(final HttpHost host,
+                            final HttpClientContext clientContext) {
+        final AuthCache authCache = clientContext.getAuthCache();
+        if (authCache != null) {
+            if (LOG.isDebugEnabled()) {
+                final String exchangeId = clientContext.getExchangeId();
+                LOG.debug("{} Clearing cached auth scheme for {}", exchangeId, host);
+            }
+            authCache.remove(host);
+        }
+    }
+
+}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/HttpAuthenticator.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/HttpAuthenticator.java
index 7987402..0a5592b 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/HttpAuthenticator.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/HttpAuthenticator.java
@@ -35,18 +35,15 @@ import java.util.Map;
 import java.util.Queue;
 
 import org.apache.hc.client5.http.AuthenticationStrategy;
-import org.apache.hc.client5.http.auth.AuthCache;
 import org.apache.hc.client5.http.auth.AuthChallenge;
 import org.apache.hc.client5.http.auth.AuthExchange;
 import org.apache.hc.client5.http.auth.AuthScheme;
-import org.apache.hc.client5.http.auth.AuthStateCacheable;
 import org.apache.hc.client5.http.auth.AuthenticationException;
 import org.apache.hc.client5.http.auth.ChallengeType;
 import org.apache.hc.client5.http.auth.CredentialsProvider;
 import org.apache.hc.client5.http.auth.MalformedChallengeException;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.core5.annotation.Contract;
-import org.apache.hc.core5.annotation.Internal;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
 import org.apache.hc.core5.http.FormattedHeader;
 import org.apache.hc.core5.http.Header;
@@ -66,6 +63,9 @@ import org.slf4j.LoggerFactory;
 
 /**
  * Utility class that implements commons aspects of the client side HTTP authentication.
+ * <p>
+ * Please note that since version 5.2 this class no longer updated the authentication cache
+ * bound to the execution context.
  *
  * @since 4.3
  */
@@ -77,15 +77,9 @@ public final class HttpAuthenticator {
     private final Logger log;
     private final AuthChallengeParser parser;
 
-    @Internal
-    public HttpAuthenticator(final Logger log) {
-        super();
-        this.log = log != null ? log : DEFAULT_LOGGER;
-        this.parser = new AuthChallengeParser();
-    }
-
     public HttpAuthenticator() {
-        this(null);
+        this.log = DEFAULT_LOGGER;
+        this.parser = new AuthChallengeParser();
     }
 
     /**
@@ -124,9 +118,6 @@ public final class HttpAuthenticator {
             if (log.isDebugEnabled()) {
                 log.debug("{} Authentication required", exchangeId);
             }
-            if (authExchange.getState() == AuthExchange.State.SUCCESS) {
-                clearCache(host, clientContext);
-            }
             return true;
         }
         switch (authExchange.getState()) {
@@ -136,7 +127,6 @@ public final class HttpAuthenticator {
                 log.debug("{} Authentication succeeded", exchangeId);
             }
             authExchange.setState(AuthExchange.State.SUCCESS);
-            updateCache(host, authExchange.getAuthScheme(), clientContext);
             break;
         case SUCCESS:
             break;
@@ -213,7 +203,6 @@ public final class HttpAuthenticator {
             if (log.isDebugEnabled()) {
                 log.debug("{} Response contains no valid authentication challenges", exchangeId);
             }
-            clearCache(host, clientContext);
             authExchange.reset();
             return false;
         }
@@ -242,15 +231,14 @@ public final class HttpAuthenticator {
                             if (log.isWarnEnabled()) {
                                 log.warn("{} {}", exchangeId, ex.getMessage());
                             }
-                            clearCache(host, clientContext);
                             authExchange.reset();
+                            authExchange.setState(AuthExchange.State.FAILURE);
                             return false;
                         }
                         if (authScheme.isChallengeComplete()) {
                             if (log.isDebugEnabled()) {
                                 log.debug("{} Authentication failed", exchangeId);
                             }
-                            clearCache(host, clientContext);
                             authExchange.reset();
                             authExchange.setState(AuthExchange.State.FAILURE);
                             return false;
@@ -376,32 +364,4 @@ public final class HttpAuthenticator {
         }
     }
 
-    private void updateCache(final HttpHost host, final AuthScheme authScheme, final HttpClientContext clientContext) {
-        final boolean cacheable = authScheme.getClass().getAnnotation(AuthStateCacheable.class) != null;
-        if (cacheable) {
-            AuthCache authCache = clientContext.getAuthCache();
-            if (authCache == null) {
-                authCache = new BasicAuthCache();
-                clientContext.setAuthCache(authCache);
-            }
-            if (log.isDebugEnabled()) {
-                final String exchangeId = clientContext.getExchangeId();
-                log.debug("{} Caching '{}' auth scheme for {}", exchangeId, authScheme.getName(), host);
-            }
-            authCache.put(host, authScheme);
-        }
-    }
-
-    private void clearCache(final HttpHost host, final HttpClientContext clientContext) {
-
-        final AuthCache authCache = clientContext.getAuthCache();
-        if (authCache != null) {
-            if (log.isDebugEnabled()) {
-                final String exchangeId = clientContext.getExchangeId();
-                log.debug("{} Clearing cached auth scheme for {}", exchangeId, host);
-            }
-            authCache.remove(host);
-        }
-    }
-
 }
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ConnectExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ConnectExec.java
index 357915f..5b80e21 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ConnectExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ConnectExec.java
@@ -32,6 +32,7 @@ import java.io.IOException;
 import org.apache.hc.client5.http.AuthenticationStrategy;
 import org.apache.hc.client5.http.HttpRoute;
 import org.apache.hc.client5.http.RouteTracker;
+import org.apache.hc.client5.http.SchemePortResolver;
 import org.apache.hc.client5.http.auth.AuthExchange;
 import org.apache.hc.client5.http.auth.ChallengeType;
 import org.apache.hc.client5.http.classic.ExecChain;
@@ -39,6 +40,7 @@ import org.apache.hc.client5.http.classic.ExecChainHandler;
 import org.apache.hc.client5.http.classic.ExecRuntime;
 import org.apache.hc.client5.http.config.RequestConfig;
 import org.apache.hc.client5.http.impl.TunnelRefusedException;
+import org.apache.hc.client5.http.impl.auth.AuthCacheKeeper;
 import org.apache.hc.client5.http.impl.auth.HttpAuthenticator;
 import org.apache.hc.client5.http.impl.routing.BasicRouteDirector;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
@@ -82,20 +84,24 @@ public final class ConnectExec implements ExecChainHandler {
     private final HttpProcessor proxyHttpProcessor;
     private final AuthenticationStrategy proxyAuthStrategy;
     private final HttpAuthenticator authenticator;
+    private final AuthCacheKeeper authCacheKeeper;
     private final HttpRouteDirector routeDirector;
 
     public ConnectExec(
             final ConnectionReuseStrategy reuseStrategy,
             final HttpProcessor proxyHttpProcessor,
-            final AuthenticationStrategy proxyAuthStrategy) {
+            final AuthenticationStrategy proxyAuthStrategy,
+            final SchemePortResolver schemePortResolver,
+            final boolean authCachingDisabled) {
         Args.notNull(reuseStrategy, "Connection reuse strategy");
         Args.notNull(proxyHttpProcessor, "Proxy HTTP processor");
         Args.notNull(proxyAuthStrategy, "Proxy authentication strategy");
-        this.reuseStrategy      = reuseStrategy;
+        this.reuseStrategy = reuseStrategy;
         this.proxyHttpProcessor = proxyHttpProcessor;
-        this.proxyAuthStrategy  = proxyAuthStrategy;
-        this.authenticator      = new HttpAuthenticator(LOG);
-        this.routeDirector      = new BasicRouteDirector();
+        this.proxyAuthStrategy = proxyAuthStrategy;
+        this.authenticator = new HttpAuthenticator();
+        this.authCacheKeeper = authCachingDisabled ? null : new AuthCacheKeeper(schemePortResolver);
+        this.routeDirector = new BasicRouteDirector();
     }
 
     @Override
@@ -207,6 +213,11 @@ public final class ConnectExec implements ExecChainHandler {
         final HttpHost target = route.getTargetHost();
         final HttpHost proxy = route.getProxyHost();
         final AuthExchange proxyAuthExchange = context.getAuthExchange(proxy);
+
+        if (authCacheKeeper != null) {
+            authCacheKeeper.loadPreemptively(proxy, proxyAuthExchange, context);
+        }
+
         ClassicHttpResponse response = null;
 
         final String authority = target.toHostString();
@@ -239,10 +250,24 @@ public final class ConnectExec implements ExecChainHandler {
             }
 
             if (config.isAuthenticationEnabled()) {
-                if (this.authenticator.isChallenged(proxy, ChallengeType.PROXY, response,
-                        proxyAuthExchange, context)) {
-                    if (this.authenticator.updateAuthState(proxy, ChallengeType.PROXY, response,
-                            this.proxyAuthStrategy, proxyAuthExchange, context)) {
+                final boolean proxyAuthRequested = authenticator.isChallenged(proxy, ChallengeType.PROXY, response, proxyAuthExchange, context);
+
+                if (authCacheKeeper != null) {
+                    if (proxyAuthRequested) {
+                        authCacheKeeper.updateOnChallenge(proxy, proxyAuthExchange, context);
+                    } else {
+                        authCacheKeeper.updateOnNoChallenge(proxy, proxyAuthExchange, context);
+                    }
+                }
+
+                if (proxyAuthRequested) {
+                    final boolean updated = authenticator.updateAuthState(proxy, ChallengeType.PROXY, response,
+                            proxyAuthStrategy, proxyAuthExchange, context);
+
+                    if (authCacheKeeper != null) {
+                        authCacheKeeper.updateOnResponse(proxy, proxyAuthExchange, context);
+                    }
+                    if (updated) {
                         // Retry request
                         response = null;
                     }
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java
index 13e7b3b..d40d077 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java
@@ -77,7 +77,6 @@ import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner;
 import org.apache.hc.client5.http.io.HttpClientConnectionManager;
 import org.apache.hc.client5.http.protocol.RedirectStrategy;
 import org.apache.hc.client5.http.protocol.RequestAddCookies;
-import org.apache.hc.client5.http.protocol.RequestAuthCache;
 import org.apache.hc.client5.http.protocol.RequestClientConnControl;
 import org.apache.hc.client5.http.protocol.RequestDefaultHeaders;
 import org.apache.hc.client5.http.protocol.RequestExpectContinue;
@@ -791,7 +790,9 @@ public class HttpClientBuilder {
                 new ConnectExec(
                         reuseStrategyCopy,
                         new DefaultHttpProcessor(new RequestTargetHost(), new RequestUserAgent(userAgentCopy)),
-                        proxyAuthStrategyCopy),
+                        proxyAuthStrategyCopy,
+                        schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE,
+                        authCachingDisabled),
                 ChainElement.CONNECT.name());
 
         final HttpProcessorBuilder b = HttpProcessorBuilder.create();
@@ -819,9 +820,6 @@ public class HttpClientBuilder {
         if (!cookieManagementDisabled) {
             b.add(new RequestAddCookies());
         }
-        if (!authCachingDisabled) {
-            b.add(new RequestAuthCache());
-        }
         if (!cookieManagementDisabled) {
             b.add(new ResponseProcessCookies());
         }
@@ -841,7 +839,12 @@ public class HttpClientBuilder {
         }
         final HttpProcessor httpProcessor = b.build();
         execChainDefinition.addFirst(
-                new ProtocolExec(httpProcessor, targetAuthStrategyCopy, proxyAuthStrategyCopy),
+                new ProtocolExec(
+                        httpProcessor,
+                        targetAuthStrategyCopy,
+                        proxyAuthStrategyCopy,
+                        schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE,
+                        authCachingDisabled),
                 ChainElement.PROTOCOL.name());
 
         // Add request retry executor, if not disabled
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProtocolExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProtocolExec.java
index 14fcbfb..556f7a6 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProtocolExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProtocolExec.java
@@ -32,6 +32,7 @@ import java.util.Iterator;
 
 import org.apache.hc.client5.http.AuthenticationStrategy;
 import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.SchemePortResolver;
 import org.apache.hc.client5.http.auth.AuthExchange;
 import org.apache.hc.client5.http.auth.ChallengeType;
 import org.apache.hc.client5.http.auth.CredentialsProvider;
@@ -41,6 +42,7 @@ import org.apache.hc.client5.http.classic.ExecChainHandler;
 import org.apache.hc.client5.http.classic.ExecRuntime;
 import org.apache.hc.client5.http.config.RequestConfig;
 import org.apache.hc.client5.http.impl.AuthSupport;
+import org.apache.hc.client5.http.impl.auth.AuthCacheKeeper;
 import org.apache.hc.client5.http.impl.auth.HttpAuthenticator;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.core5.annotation.Contract;
@@ -86,15 +88,19 @@ public final class ProtocolExec implements ExecChainHandler {
     private final AuthenticationStrategy targetAuthStrategy;
     private final AuthenticationStrategy proxyAuthStrategy;
     private final HttpAuthenticator authenticator;
+    private final AuthCacheKeeper authCacheKeeper;
 
     public ProtocolExec(
             final HttpProcessor httpProcessor,
             final AuthenticationStrategy targetAuthStrategy,
-            final AuthenticationStrategy proxyAuthStrategy) {
+            final AuthenticationStrategy proxyAuthStrategy,
+            final SchemePortResolver schemePortResolver,
+            final boolean authCachingDisabled) {
         this.httpProcessor = Args.notNull(httpProcessor, "HTTP protocol processor");
         this.targetAuthStrategy = Args.notNull(targetAuthStrategy, "Target authentication strategy");
         this.proxyAuthStrategy = Args.notNull(proxyAuthStrategy, "Proxy authentication strategy");
         this.authenticator = new HttpAuthenticator();
+        this.authCacheKeeper = authCachingDisabled ? null : new AuthCacheKeeper(schemePortResolver);
     }
 
     @Override
@@ -149,6 +155,13 @@ public final class ProtocolExec implements ExecChainHandler {
             final AuthExchange targetAuthExchange = context.getAuthExchange(target);
             final AuthExchange proxyAuthExchange = proxy != null ? context.getAuthExchange(proxy) : new AuthExchange();
 
+            if (authCacheKeeper != null) {
+                authCacheKeeper.loadPreemptively(target, targetAuthExchange, context);
+                if (proxy != null) {
+                    authCacheKeeper.loadPreemptively(proxy, proxyAuthExchange, context);
+                }
+            }
+
             RequestEntityProxy.enhance(request);
 
             for (;;) {
@@ -188,7 +201,13 @@ public final class ProtocolExec implements ExecChainHandler {
                     }
                     return response;
                 }
-                if (needAuthentication(targetAuthExchange, proxyAuthExchange, route, request, response, context)) {
+                if (needAuthentication(
+                        targetAuthExchange,
+                        proxyAuthExchange,
+                        proxy != null ? proxy : target,
+                        target,
+                        response,
+                        context)) {
                     // Make sure the response body is fully consumed, if present
                     final HttpEntity responseEntity = response.getEntity();
                     if (execRuntime.isConnectionReusable()) {
@@ -238,31 +257,53 @@ public final class ProtocolExec implements ExecChainHandler {
     private boolean needAuthentication(
             final AuthExchange targetAuthExchange,
             final AuthExchange proxyAuthExchange,
-            final HttpRoute route,
-            final ClassicHttpRequest request,
+            final HttpHost proxy,
+            final HttpHost target,
             final HttpResponse response,
             final HttpClientContext context) {
         final RequestConfig config = context.getRequestConfig();
         if (config.isAuthenticationEnabled()) {
-            final HttpHost target = AuthSupport.resolveAuthTarget(request, route);
             final boolean targetAuthRequested = authenticator.isChallenged(
                     target, ChallengeType.TARGET, response, targetAuthExchange, context);
 
-            HttpHost proxy = route.getProxyHost();
-            // if proxy is not set use target host instead
-            if (proxy == null) {
-                proxy = route.getTargetHost();
+            if (authCacheKeeper != null) {
+                if (targetAuthRequested) {
+                    authCacheKeeper.updateOnChallenge(target, targetAuthExchange, context);
+                } else {
+                    authCacheKeeper.updateOnNoChallenge(target, targetAuthExchange, context);
+                }
             }
+
             final boolean proxyAuthRequested = authenticator.isChallenged(
                     proxy, ChallengeType.PROXY, response, proxyAuthExchange, context);
 
+            if (authCacheKeeper != null) {
+                if (proxyAuthRequested) {
+                    authCacheKeeper.updateOnChallenge(proxy, proxyAuthExchange, context);
+                } else {
+                    authCacheKeeper.updateOnNoChallenge(proxy, proxyAuthExchange, context);
+                }
+            }
+
             if (targetAuthRequested) {
-                return authenticator.updateAuthState(target, ChallengeType.TARGET, response,
+                final boolean updated = authenticator.updateAuthState(target, ChallengeType.TARGET, response,
                         targetAuthStrategy, targetAuthExchange, context);
+
+                if (authCacheKeeper != null) {
+                    authCacheKeeper.updateOnResponse(target, targetAuthExchange, context);
+                }
+
+                return updated;
             }
             if (proxyAuthRequested) {
-                return authenticator.updateAuthState(proxy, ChallengeType.PROXY, response,
+                final boolean updated = authenticator.updateAuthState(proxy, ChallengeType.PROXY, response,
                         proxyAuthStrategy, proxyAuthExchange, context);
+
+                if (authCacheKeeper != null) {
+                    authCacheKeeper.updateOnResponse(proxy, proxyAuthExchange, context);
+                }
+
+                return updated;
             }
         }
         return false;
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/protocol/RequestAuthCache.java b/httpclient5/src/main/java/org/apache/hc/client5/http/protocol/RequestAuthCache.java
index 597bb21..608357d 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/protocol/RequestAuthCache.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/protocol/RequestAuthCache.java
@@ -34,6 +34,7 @@ import org.apache.hc.client5.http.auth.AuthCache;
 import org.apache.hc.client5.http.auth.AuthExchange;
 import org.apache.hc.client5.http.auth.AuthScheme;
 import org.apache.hc.client5.http.auth.CredentialsProvider;
+import org.apache.hc.client5.http.impl.RequestSupport;
 import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
 import org.apache.hc.core5.http.EntityDetails;
@@ -42,7 +43,6 @@ import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpRequestInterceptor;
 import org.apache.hc.core5.http.protocol.HttpContext;
-import org.apache.hc.core5.net.URIAuthority;
 import org.apache.hc.core5.util.Args;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -53,7 +53,10 @@ import org.slf4j.LoggerFactory;
  * {@link AuthCache} associated with the given target or proxy host.
  *
  * @since 4.1
+ *
+ * @deprecated Do not use.
  */
+@Deprecated
 @Contract(threading = ThreadingBehavior.STATELESS)
 public class RequestAuthCache implements HttpRequestInterceptor {
 
@@ -96,19 +99,11 @@ public class RequestAuthCache implements HttpRequestInterceptor {
             return;
         }
 
-        final URIAuthority authority = request.getAuthority();
-        final HttpHost target;
-        if (authority != null) {
-            target = new HttpHost(
-                    request.getScheme(),
-                    authority.getHostName(),
-                    authority.getPort() >= 0 ? authority.getPort() : route.getTargetHost().getPort());
-        } else {
-            target = route.getTargetHost();
-        }
+        final HttpHost target = new HttpHost(request.getScheme(), request.getAuthority());
         final AuthExchange targetAuthExchange = clientContext.getAuthExchange(target);
         if (targetAuthExchange.getState() == AuthExchange.State.UNCHALLENGED) {
-            final AuthScheme authScheme = authCache.get(target);
+            final String pathPrefix = RequestSupport.extractPathPrefix(request);
+            final AuthScheme authScheme = authCache.get(target, pathPrefix);
             if (authScheme != null) {
                 if (LOG.isDebugEnabled()) {
                     LOG.debug("{} Re-using cached '{}' auth scheme for {}", exchangeId, authScheme.getName(), target);
@@ -121,7 +116,7 @@ public class RequestAuthCache implements HttpRequestInterceptor {
         if (proxy != null) {
             final AuthExchange proxyAuthExchange = clientContext.getAuthExchange(proxy);
             if (proxyAuthExchange.getState() == AuthExchange.State.UNCHALLENGED) {
-                final AuthScheme authScheme = authCache.get(proxy);
+                final AuthScheme authScheme = authCache.get(proxy, null);
                 if (authScheme != null) {
                     if (LOG.isDebugEnabled()) {
                         LOG.debug("{} Re-using cached '{}' auth scheme for {}", exchangeId, authScheme.getName(), proxy);
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestHttpAuthenticator.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestHttpAuthenticator.java
index fd24894..10e6946 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestHttpAuthenticator.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestHttpAuthenticator.java
@@ -29,7 +29,6 @@ package org.apache.hc.client5.http.impl.auth;
 import java.util.LinkedList;
 import java.util.Queue;
 
-import org.apache.hc.client5.http.auth.AuthCache;
 import org.apache.hc.client5.http.auth.AuthExchange;
 import org.apache.hc.client5.http.auth.AuthScheme;
 import org.apache.hc.client5.http.auth.AuthSchemeFactory;
@@ -80,7 +79,6 @@ public class TestHttpAuthenticator {
     private HttpHost defaultHost;
     private CredentialsProvider credentialsProvider;
     private Lookup<AuthSchemeFactory> authSchemeRegistry;
-    private AuthCache authCache;
     private HttpAuthenticator httpAuthenticator;
 
     @Before
@@ -98,8 +96,6 @@ public class TestHttpAuthenticator {
             .register(StandardAuthScheme.DIGEST, DigestSchemeFactory.INSTANCE)
             .register(StandardAuthScheme.NTLM, NTLMSchemeFactory.INSTANCE).build();
         this.context.setAttribute(HttpClientContext.AUTHSCHEME_REGISTRY, this.authSchemeRegistry);
-        this.authCache = Mockito.mock(AuthCache.class);
-        this.context.setAttribute(HttpClientContext.AUTH_CACHE, this.authCache);
         this.httpAuthenticator = new HttpAuthenticator();
     }
 
@@ -109,7 +105,6 @@ public class TestHttpAuthenticator {
         response.setHeader(HttpHeaders.WWW_AUTHENTICATE, StandardAuthScheme.BASIC + " realm=test");
         Assert.assertTrue(this.httpAuthenticator.isChallenged(
                 this.defaultHost, ChallengeType.TARGET, response, this.authExchange, this.context));
-        Mockito.verifyNoInteractions(this.authCache);
     }
 
     @Test
@@ -122,8 +117,6 @@ public class TestHttpAuthenticator {
 
         Assert.assertTrue(this.httpAuthenticator.isChallenged(
                 this.defaultHost, ChallengeType.TARGET, response, this.authExchange, this.context));
-
-        Mockito.verify(this.authCache).remove(this.defaultHost);
     }
 
     @Test
@@ -144,8 +137,6 @@ public class TestHttpAuthenticator {
         Assert.assertFalse(this.httpAuthenticator.isChallenged(
                 this.defaultHost, ChallengeType.TARGET, response, this.authExchange, this.context));
         Assert.assertEquals(AuthExchange.State.SUCCESS, this.authExchange.getState());
-
-        Mockito.verify(this.authCache).put(this.defaultHost, this.authScheme);
     }
 
     @Test
@@ -157,8 +148,6 @@ public class TestHttpAuthenticator {
         Assert.assertFalse(this.httpAuthenticator.isChallenged(
                 this.defaultHost, ChallengeType.TARGET, response, this.authExchange, this.context));
         Assert.assertEquals(AuthExchange.State.SUCCESS, this.authExchange.getState());
-
-        Mockito.verify(this.authCache).put(this.defaultHost, this.authScheme);
     }
 
     @Test
@@ -269,8 +258,6 @@ public class TestHttpAuthenticator {
                 host, ChallengeType.TARGET, response, authStrategy, this.authExchange, this.context));
 
         Assert.assertEquals(AuthExchange.State.FAILURE, this.authExchange.getState());
-
-        Mockito.verify(this.authCache).remove(host);
     }
 
     @Test
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestConnectExec.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestConnectExec.java
index 730fbd1..1a743bb 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestConnectExec.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestConnectExec.java
@@ -87,7 +87,7 @@ public class TestConnectExec {
 
     @Before
     public void setup() throws Exception {
-        exec = new ConnectExec(reuseStrategy, proxyHttpProcessor, proxyAuthStrategy);
+        exec = new ConnectExec(reuseStrategy, proxyHttpProcessor, proxyAuthStrategy, null, true);
         target = new HttpHost("foo", 80);
         proxy = new HttpHost("bar", 8888);
     }
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestProtocolExec.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestProtocolExec.java
index 3e687cd..e0d78dc 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestProtocolExec.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestProtocolExec.java
@@ -89,7 +89,7 @@ public class TestProtocolExec {
 
     @Before
     public void setup() throws Exception {
-        protocolExec = new ProtocolExec(httpProcessor, targetAuthStrategy, proxyAuthStrategy);
+        protocolExec = new ProtocolExec(httpProcessor, targetAuthStrategy, proxyAuthStrategy, null, true);
         target = new HttpHost("foo", 80);
         proxy = new HttpHost("bar", 8888);
     }