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 000000000..7e57753a9 --- /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 + * . + * + */ + +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 cookieSpecRegistry; + private Lookup authSchemeRegistry; + private CookieStore cookieStore; + private CredentialsProvider credentialsProvider; + private AuthCache authCache; + private Map 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 cookieSpecRegistry) { + this.cookieSpecRegistry = cookieSpecRegistry; + return this; + } + + public ContextBuilder useAuthSchemeRegistry(final Lookup 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 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 c4d11b47f..e3584cfeb 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 90433462e..3728d866b 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 000000000..4670b2f58 --- /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 + * . + * + */ +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 future = httpclient.execute( + SimpleRequestProducer.create(request), + SimpleResponseConsumer.create(), + localContext, + new FutureCallback() { + + @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 a3c608d88..e0671f442 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 a7eefb239..dff858631 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 8799912fe..6f4a864da 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 69b3a0490..f056fb60e 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");