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 2022/02/13 19:17:36 UTC

[httpcomponents-client] 01/01: HTTPCLIENT-2203: Corrected target host normalization by the request execution interceptors; added ContextBuilder with support for preemptive authentication initialization

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 19626731c0016f6a0dccaabad8ff2eac00c416a3
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sun Feb 13 18:51:34 2022 +0100

    HTTPCLIENT-2203: Corrected target host normalization by the request execution interceptors; added ContextBuilder with support for preemptive authentication initialization
---
 .../org/apache/hc/client5/http/ContextBuilder.java | 134 +++++++++++++++++++++
 .../client5/http/impl/async/AsyncProtocolExec.java |  10 +-
 .../hc/client5/http/impl/classic/ProtocolExec.java |  10 +-
 .../AsyncPreemptiveBasicClientAuthentication.java  |  95 +++++++++++++++
 .../client5/http/examples/ClientConfiguration.java |   8 +-
 .../client5/http/examples/ClientCustomContext.java |   8 +-
 .../ClientPreemptiveBasicAuthentication.java       |  15 +--
 .../ClientPreemptiveDigestAuthentication.java      |  10 +-
 8 files changed, 266 insertions(+), 24 deletions(-)

diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/ContextBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/ContextBuilder.java
new file mode 100644
index 0000000..7e57753
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/ContextBuilder.java
@@ -0,0 +1,134 @@
+/*
+ * ====================================================================
+ * 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;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.hc.client5.http.auth.AuthCache;
+import org.apache.hc.client5.http.auth.AuthScheme;
+import org.apache.hc.client5.http.auth.AuthSchemeFactory;
+import org.apache.hc.client5.http.auth.CredentialsProvider;
+import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
+import org.apache.hc.client5.http.cookie.CookieSpecFactory;
+import org.apache.hc.client5.http.cookie.CookieStore;
+import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
+import org.apache.hc.client5.http.impl.auth.BasicScheme;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.routing.RoutingSupport;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.config.Lookup;
+import org.apache.hc.core5.http.protocol.BasicHttpContext;
+import org.apache.hc.core5.util.Args;
+
+/**
+ * {@link HttpClientContext} builder.
+ *
+ * @since 5.2
+ */
+public class ContextBuilder {
+
+    private final SchemePortResolver schemePortResolver;
+
+    private Lookup<CookieSpecFactory> cookieSpecRegistry;
+    private Lookup<AuthSchemeFactory> authSchemeRegistry;
+    private CookieStore cookieStore;
+    private CredentialsProvider credentialsProvider;
+    private AuthCache authCache;
+    private Map<HttpHost, AuthScheme> authSchemeMap;
+
+    ContextBuilder(final SchemePortResolver schemePortResolver) {
+        this.schemePortResolver = schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE;
+    }
+
+    public static ContextBuilder create(final SchemePortResolver schemePortResolver) {
+        return new ContextBuilder(schemePortResolver);
+    }
+
+    public static ContextBuilder create() {
+        return new ContextBuilder(DefaultSchemePortResolver.INSTANCE);
+    }
+
+    public ContextBuilder useCookieSpecRegistry(final Lookup<CookieSpecFactory> cookieSpecRegistry) {
+        this.cookieSpecRegistry = cookieSpecRegistry;
+        return this;
+    }
+
+    public ContextBuilder useAuthSchemeRegistry(final Lookup<AuthSchemeFactory> authSchemeRegistry) {
+        this.authSchemeRegistry = authSchemeRegistry;
+        return this;
+    }
+
+    public ContextBuilder useCookieStore(final CookieStore cookieStore) {
+        this.cookieStore = cookieStore;
+        return this;
+    }
+
+    public ContextBuilder useCredentialsProvider(final CredentialsProvider credentialsProvider) {
+        this.credentialsProvider = credentialsProvider;
+        return this;
+    }
+
+    public ContextBuilder useAuthCache(final AuthCache authCache) {
+        this.authCache = authCache;
+        return this;
+    }
+
+    public ContextBuilder preemptiveAuth(final HttpHost host, final AuthScheme authScheme) {
+        Args.notNull(host, "HTTP host");
+        if (authSchemeMap == null) {
+            authSchemeMap = new HashMap<>();
+        }
+        authSchemeMap.put(RoutingSupport.normalize(host, schemePortResolver), authScheme);
+        return this;
+    }
+
+    public ContextBuilder preemptiveBasicAuth(final HttpHost host, final UsernamePasswordCredentials credentials) {
+        Args.notNull(host, "HTTP host");
+        final BasicScheme authScheme = new BasicScheme(StandardCharsets.UTF_8);
+        authScheme.initPreemptive(credentials);
+        preemptiveAuth(host, authScheme);
+        return this;
+    }
+    public HttpClientContext build() {
+        final HttpClientContext context = new HttpClientContext(new BasicHttpContext());
+        context.setCookieSpecRegistry(cookieSpecRegistry);
+        context.setAuthSchemeRegistry(authSchemeRegistry);
+        context.setCookieStore(cookieStore);
+        context.setCredentialsProvider(credentialsProvider);
+        context.setAuthCache(authCache);
+        if (authSchemeMap != null) {
+            for (final Map.Entry<HttpHost, AuthScheme> entry : authSchemeMap.entrySet()) {
+                context.resetAuthExchange(entry.getKey(), entry.getValue());
+            }
+        }
+        return context;
+    }
+
+}
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 c4d11b4..e3584cf 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
@@ -40,6 +40,7 @@ import org.apache.hc.client5.http.async.AsyncExecRuntime;
 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.DefaultSchemePortResolver;
 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;
@@ -87,6 +88,7 @@ public final class AsyncProtocolExec implements AsyncExecChainHandler {
     private final AuthenticationStrategy targetAuthStrategy;
     private final AuthenticationStrategy proxyAuthStrategy;
     private final HttpAuthenticator authenticator;
+    private final SchemePortResolver schemePortResolver;
     private final AuthCacheKeeper authCacheKeeper;
 
     AsyncProtocolExec(
@@ -99,7 +101,8 @@ public final class AsyncProtocolExec implements AsyncExecChainHandler {
         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);
+        this.schemePortResolver = schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE;
+        this.authCacheKeeper = authCachingDisabled ? null : new AuthCacheKeeper(this.schemePortResolver);
     }
 
     @Override
@@ -144,7 +147,10 @@ public final class AsyncProtocolExec implements AsyncExecChainHandler {
             throw new ProtocolException("Request URI authority contains deprecated userinfo component");
         }
 
-        final HttpHost target = new HttpHost(request.getScheme(), request.getAuthority());
+        final HttpHost target = new HttpHost(
+                request.getScheme(),
+                authority.getHostName(),
+                schemePortResolver.resolve(request.getScheme(), authority));
         final String pathPrefix = RequestSupport.extractPathPrefix(request);
         final AuthExchange targetAuthExchange = clientContext.getAuthExchange(target);
         final AuthExchange proxyAuthExchange = proxy != null ? clientContext.getAuthExchange(proxy) : new AuthExchange();
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 9043346..3728d86 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
@@ -39,6 +39,7 @@ import org.apache.hc.client5.http.classic.ExecChain;
 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.DefaultSchemePortResolver;
 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;
@@ -86,6 +87,7 @@ public final class ProtocolExec implements ExecChainHandler {
     private final AuthenticationStrategy targetAuthStrategy;
     private final AuthenticationStrategy proxyAuthStrategy;
     private final HttpAuthenticator authenticator;
+    private final SchemePortResolver schemePortResolver;
     private final AuthCacheKeeper authCacheKeeper;
 
     public ProtocolExec(
@@ -98,7 +100,8 @@ public final class ProtocolExec implements ExecChainHandler {
         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);
+        this.schemePortResolver = schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE;
+        this.authCacheKeeper = authCachingDisabled ? null : new AuthCacheKeeper(this.schemePortResolver);
     }
 
     @Override
@@ -147,7 +150,10 @@ public final class ProtocolExec implements ExecChainHandler {
                 throw new ProtocolException("Request URI authority contains deprecated userinfo component");
             }
 
-            final HttpHost target = new HttpHost(request.getScheme(), request.getAuthority());
+            final HttpHost target = new HttpHost(
+                    request.getScheme(),
+                    authority.getHostName(),
+                    schemePortResolver.resolve(request.getScheme(), authority));
             final String pathPrefix = RequestSupport.extractPathPrefix(request);
 
             final AuthExchange targetAuthExchange = context.getAuthExchange(target);
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncPreemptiveBasicClientAuthentication.java b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncPreemptiveBasicClientAuthentication.java
new file mode 100644
index 0000000..4670b2f
--- /dev/null
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncPreemptiveBasicClientAuthentication.java
@@ -0,0 +1,95 @@
+/*
+ * ====================================================================
+ * 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.examples;
+
+import java.util.concurrent.Future;
+
+import org.apache.hc.client5.http.ContextBuilder;
+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.async.methods.SimpleRequestProducer;
+import org.apache.hc.client5.http.async.methods.SimpleResponseConsumer;
+import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.core5.concurrent.FutureCallback;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.message.StatusLine;
+import org.apache.hc.core5.io.CloseMode;
+
+/**
+ * A simple example that uses HttpClient to execute an HTTP request against
+ * a target site that requires user authentication.
+ */
+public class AsyncPreemptiveBasicClientAuthentication {
+
+    public static void main(final String[] args) throws Exception {
+        final CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault();
+        httpclient.start();
+
+        final HttpClientContext localContext = ContextBuilder.create()
+                .preemptiveBasicAuth(new HttpHost("http", "httpbin.org"),
+                        new UsernamePasswordCredentials("user", "passwd".toCharArray()))
+                .build();
+
+        final SimpleHttpRequest request = SimpleRequestBuilder.get("http://httpbin.org:80/basic-auth/user/passwd")
+                .build();
+
+        System.out.println("Executing request " + request);
+        for (int i = 0; i < 3; i++) {
+            final Future<SimpleHttpResponse> future = httpclient.execute(
+                    SimpleRequestProducer.create(request),
+                    SimpleResponseConsumer.create(),
+                    localContext,
+                    new FutureCallback<SimpleHttpResponse>() {
+
+                        @Override
+                        public void completed(final SimpleHttpResponse response) {
+                            System.out.println(request + "->" + new StatusLine(response));
+                            System.out.println(response.getBody());
+                        }
+
+                        @Override
+                        public void failed(final Exception ex) {
+                            System.out.println(request + "->" + ex);
+                        }
+
+                        @Override
+                        public void cancelled() {
+                            System.out.println(request + " cancelled");
+                        }
+
+                    });
+            future.get();
+        }
+
+        System.out.println("Shutting down");
+        httpclient.close(CloseMode.GRACEFUL);
+    }
+}
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientConfiguration.java b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientConfiguration.java
index a3c608d..e0671f4 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientConfiguration.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientConfiguration.java
@@ -36,6 +36,7 @@ import java.util.Collections;
 
 import javax.net.ssl.SSLContext;
 
+import org.apache.hc.client5.http.ContextBuilder;
 import org.apache.hc.client5.http.DnsResolver;
 import org.apache.hc.client5.http.HttpRoute;
 import org.apache.hc.client5.http.SystemDefaultDnsResolver;
@@ -231,11 +232,12 @@ public class ClientConfiguration {
             httpget.setConfig(requestConfig);
 
             // Execution context can be customized locally.
-            final HttpClientContext context = HttpClientContext.create();
             // Contextual attributes set the local context level will take
             // precedence over those set at the client level.
-            context.setCookieStore(cookieStore);
-            context.setCredentialsProvider(credentialsProvider);
+            final HttpClientContext context = ContextBuilder.create()
+                    .useCookieStore(cookieStore)
+                    .useCredentialsProvider(credentialsProvider)
+                    .build();
 
             System.out.println("Executing request " + httpget.getMethod() + " " + httpget.getUri());
             httpclient.execute(httpget, context, response -> {
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientCustomContext.java b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientCustomContext.java
index a7eefb2..dff8586 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientCustomContext.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientCustomContext.java
@@ -29,6 +29,7 @@ package org.apache.hc.client5.http.examples;
 
 import java.util.List;
 
+import org.apache.hc.client5.http.ContextBuilder;
 import org.apache.hc.client5.http.classic.methods.HttpGet;
 import org.apache.hc.client5.http.cookie.BasicCookieStore;
 import org.apache.hc.client5.http.cookie.Cookie;
@@ -51,9 +52,10 @@ public class ClientCustomContext {
             final CookieStore cookieStore = new BasicCookieStore();
 
             // Create local HTTP context
-            final HttpClientContext localContext = HttpClientContext.create();
-            // Bind custom cookie store to the local context
-            localContext.setCookieStore(cookieStore);
+            final HttpClientContext localContext = ContextBuilder.create()
+                    // Bind custom cookie store to the local context
+                    .useCookieStore(cookieStore)
+                    .build();
 
             final HttpGet httpget = new HttpGet("http://httpbin.org/cookies");
             System.out.println("Executing request " + httpget.getMethod() + " " + httpget.getUri());
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientPreemptiveBasicAuthentication.java b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientPreemptiveBasicAuthentication.java
index 8799912..6f4a864 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientPreemptiveBasicAuthentication.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientPreemptiveBasicAuthentication.java
@@ -26,9 +26,9 @@
  */
 package org.apache.hc.client5.http.examples;
 
+import org.apache.hc.client5.http.ContextBuilder;
 import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
 import org.apache.hc.client5.http.classic.methods.HttpGet;
-import org.apache.hc.client5.http.impl.auth.BasicScheme;
 import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
 import org.apache.hc.client5.http.impl.classic.HttpClients;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
@@ -49,15 +49,10 @@ public class ClientPreemptiveBasicAuthentication {
     public static void main(final String[] args) throws Exception {
         try (final CloseableHttpClient httpclient = HttpClients.createDefault()) {
 
-            // Generate Basic scheme object and add it to the local auth cache
-            final BasicScheme basicAuth = new BasicScheme();
-            basicAuth.initPreemptive(new UsernamePasswordCredentials("user", "passwd".toCharArray()));
-
-            final HttpHost target = new HttpHost("http", "httpbin.org", 80);
-
-            // Add AuthCache to the execution context
-            final HttpClientContext localContext = HttpClientContext.create();
-            localContext.resetAuthExchange(target, basicAuth);
+            final HttpClientContext localContext = ContextBuilder.create()
+                    .preemptiveBasicAuth(new HttpHost("http", "httpbin.org"),
+                            new UsernamePasswordCredentials("user", "passwd".toCharArray()))
+                    .build();
 
             final HttpGet httpget = new HttpGet("http://httpbin.org/hidden-basic-auth/user/passwd");
 
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientPreemptiveDigestAuthentication.java b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientPreemptiveDigestAuthentication.java
index 69b3a04..f056fb6 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientPreemptiveDigestAuthentication.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientPreemptiveDigestAuthentication.java
@@ -26,6 +26,7 @@
  */
 package org.apache.hc.client5.http.examples;
 
+import org.apache.hc.client5.http.ContextBuilder;
 import org.apache.hc.client5.http.auth.AuthExchange;
 import org.apache.hc.client5.http.auth.AuthScheme;
 import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
@@ -52,10 +53,11 @@ public class ClientPreemptiveDigestAuthentication {
 
             final HttpHost target = new HttpHost("http", "httpbin.org", 80);
 
-            final HttpClientContext localContext = HttpClientContext.create();
-            localContext.setCredentialsProvider(CredentialsProviderBuilder.create()
-                    .add(target, new UsernamePasswordCredentials("user", "passwd".toCharArray()))
-                    .build());
+            final HttpClientContext localContext = ContextBuilder.create()
+                    .useCredentialsProvider(CredentialsProviderBuilder.create()
+                            .add(target, new UsernamePasswordCredentials("user", "passwd".toCharArray()))
+                            .build())
+                    .build();
 
             final HttpGet httpget = new HttpGet("http://httpbin.org/digest-auth/auth/user/passwd");