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/27 17:14:03 UTC
[httpcomponents-client] 02/04: AuthCache conformance to RFC 7617
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 d3ca96cca1ac05e71771a483ea617c542b1c7dcf
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 | 74 ++++++++
.../apache/hc/client5/http/SchemePortResolver.java | 10 ++
.../org/apache/hc/client5/http/auth/AuthCache.java | 61 +++++++
.../apache/hc/client5/http/auth/AuthExchange.java | 16 ++
.../http/impl/DefaultAuthenticationStrategy.java | 4 +-
.../http/impl/DefaultSchemePortResolver.java | 14 +-
.../{ProtocolSupport.java => RequestSupport.java} | 61 +++----
.../client5/http/impl/async/AsyncConnectExec.java | 8 +-
.../client5/http/impl/async/AsyncProtocolExec.java | 36 ++--
.../hc/client5/http/impl/auth/AuthCacheKeeper.java | 33 ++--
.../hc/client5/http/impl/auth/BasicAuthCache.java | 96 +++++++++--
.../hc/client5/http/impl/classic/ConnectExec.java | 8 +-
.../hc/client5/http/impl/classic/ProtocolExec.java | 31 +++-
.../hc/client5/http/impl/TestProtocolSupport.java | 55 ------
.../hc/client5/http/impl/TestRequestSupport.java | 53 ++++++
.../client5/http/impl/auth/TestBasicAuthCache.java | 2 +-
.../http/impl/auth/TestRequestAuthCache.java | 188 ---------------------
20 files changed, 527 insertions(+), 325 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..0af41e1 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,69 @@ 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();
+ final HttpClientContext context = HttpClientContext.create();
+ context.setAuthCache(authCache);
+ context.setCredentialsProvider(credentialsProvider);
+
+ for (final String requestPath: new String[] {"/blah/a", "/blah/b?huh", "/blah/c", "/bl%61h/%61"}) {
+ 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/", "/buh/a"}) {
+ 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(2)).select(Mockito.any(), Mockito.any(), Mockito.any());
+
+ MatcherAssert.assertThat(
+ responseQueue.stream().map(HttpResponse::getCode).collect(Collectors.toList()),
+ CoreMatchers.equalTo(Arrays.asList(200, 401, 200, 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/auth/AuthExchange.java b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/AuthExchange.java
index 4709149..2aaf1fb 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/AuthExchange.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/AuthExchange.java
@@ -47,6 +47,7 @@ public class AuthExchange {
private State state;
private AuthScheme authScheme;
private Queue<AuthScheme> authOptions;
+ private String pathPrefix;
public AuthExchange() {
super();
@@ -57,6 +58,7 @@ public class AuthExchange {
this.state = State.UNCHALLENGED;
this.authOptions = null;
this.authScheme = null;
+ this.pathPrefix = null;
}
public State getState() {
@@ -82,6 +84,20 @@ public class AuthExchange {
}
/**
+ * @since 5.2
+ */
+ public String getPathPrefix() {
+ return pathPrefix;
+ }
+
+ /**
+ * @since 5.2
+ */
+ public void setPathPrefix(final String pathPrefix) {
+ this.pathPrefix = pathPrefix;
+ }
+
+ /**
* Resets the auth state with {@link AuthScheme} and clears auth options.
*
* @param authScheme auth scheme. May not be null.
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultAuthenticationStrategy.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultAuthenticationStrategy.java
index ca0fdd4..a25f563 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultAuthenticationStrategy.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultAuthenticationStrategy.java
@@ -39,8 +39,8 @@ import org.apache.hc.client5.http.AuthenticationStrategy;
import org.apache.hc.client5.http.auth.AuthChallenge;
import org.apache.hc.client5.http.auth.AuthScheme;
import org.apache.hc.client5.http.auth.AuthSchemeFactory;
-import org.apache.hc.client5.http.auth.StandardAuthScheme;
import org.apache.hc.client5.http.auth.ChallengeType;
+import org.apache.hc.client5.http.auth.StandardAuthScheme;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.core5.annotation.Contract;
@@ -117,7 +117,7 @@ public class DefaultAuthenticationStrategy implements AuthenticationStrategy {
options.add(authScheme);
} else {
if (LOG.isDebugEnabled()) {
- LOG.debug("{}, Challenge for {} authentication scheme not available", exchangeId, schemeName);
+ LOG.debug("{} Challenge for {} authentication scheme not available", exchangeId, schemeName);
}
}
}
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 51%
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..50e879f 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,48 @@
*/
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) {
+ final String path = request.getPath();
+ try {
+ final URIBuilder uriBuilder = new URIBuilder(path);
+ 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 "/";
+ } else {
+ final StringBuilder buf = new StringBuilder();
+ buf.append('/');
+ for (final String pathSegment : pathSegments) {
+ PercentCodec.encode(buf, pathSegment, StandardCharsets.US_ASCII);
+ buf.append('/');
+ }
+ return buf.toString();
}
- return buf.toString();
- } else {
- return request.getPath();
+ } catch (final URISyntaxException ex) {
+ return path;
}
}
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..7e28534 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,36 @@ 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 (!targetAuthExchange.isConnectionBased() &&
+ targetAuthExchange.getPathPrefix() != null &&
+ !pathPrefix.startsWith(targetAuthExchange.getPathPrefix())) {
+ // force re-authentication if the current path prefix does not match
+ // that of the previous authentication exchange.
+ targetAuthExchange.reset();
+ }
+ if (targetAuthExchange.getPathPrefix() == null) {
+ targetAuthExchange.setPathPrefix(pathPrefix);
+ }
+
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 +230,7 @@ public final class AsyncProtocolExec implements AsyncExecChainHandler {
proxyAuthExchange,
proxy != null ? proxy : target,
target,
+ pathPrefix,
response,
clientContext)) {
challenged.set(true);
@@ -267,7 +282,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 +313,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 +323,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 +334,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 +345,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 +355,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..10ce0b6 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,39 @@ 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));
+ AuthScheme authScheme = loadFromCache(host, pathPrefix, HttpClientContext.adapt(context));
+ if (authScheme == null && pathPrefix != null) {
+ authScheme = loadFromCache(host, null, HttpClientContext.adapt(context));
+ }
if (authScheme != null) {
authExchange.select(authScheme);
}
@@ -92,14 +99,16 @@ 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 != null ? pathPrefix : "");
}
return authScheme;
}
@@ -108,6 +117,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 +129,24 @@ 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 != null ? 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 != null ? 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..95f9545 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,26 @@ 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 (!targetAuthExchange.isConnectionBased() &&
+ targetAuthExchange.getPathPrefix() != null &&
+ !pathPrefix.startsWith(targetAuthExchange.getPathPrefix())) {
+ // force re-authentication if the current path prefix does not match
+ // that of the previous authentication exchange.
+ targetAuthExchange.reset();
+ }
+ if (targetAuthExchange.getPathPrefix() == null) {
+ targetAuthExchange.setPathPrefix(pathPrefix);
+ }
+
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 +219,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 +273,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 +283,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 +294,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 +305,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 +315,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..35e787b
--- /dev/null
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestRequestSupport.java
@@ -0,0 +1,53 @@
+/*
+ * ====================================================================
+ * 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}.
+ */
+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.assertEquals("/", RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/")));
+ Assert.assertEquals("/", RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/aaaa")));
+ Assert.assertEquals("/", 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());
- }
-
-}