From 40551e00b46571a03a4610b53d7d5308c3ea3e39 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Fri, 10 Nov 2023 19:51:43 +0100 Subject: [PATCH] HttpClientFactory: Expose a method to customize the underlying HttpClient Cleanup javadoc This closes #3205 --- CHANGELOG.md | 3 ++ .../acs/commons/http/HttpClientFactory.java | 24 +++++++++--- .../http/impl/HttpClientFactoryImpl.java | 37 ++++++++++++++++--- .../adobe/acs/commons/http/package-info.java | 2 +- 4 files changed, 54 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05007e5a0d..4f5cdc9e8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com) ## Unreleased ([details][unreleased changes details]) +## Added + +- #3205 - HttpClientFactory: Expose a method to customize the underlying HttpClient ## 6.3.0 - 2023-10-25 diff --git a/bundle/src/main/java/com/adobe/acs/commons/http/HttpClientFactory.java b/bundle/src/main/java/com/adobe/acs/commons/http/HttpClientFactory.java index 2ed3ed1b7d..80c9020b99 100644 --- a/bundle/src/main/java/com/adobe/acs/commons/http/HttpClientFactory.java +++ b/bundle/src/main/java/com/adobe/acs/commons/http/HttpClientFactory.java @@ -17,21 +17,35 @@ */ package com.adobe.acs.commons.http; +import java.util.function.Consumer; + +import org.apache.http.client.HttpClient; import org.apache.http.client.fluent.Executor; import org.apache.http.client.fluent.Request; +import org.apache.http.impl.client.HttpClientBuilder; /** - * Factory for building pre-configured HttpClient Fluent Executor and Request objects - * based a configure host, port and (optionally) username/password. + * Factory for building pre-configured HttpClient Fluent {@link Executor} and {@link Request} objects + * based on a configured host, port and (optionally) username/password. * - * Factories will generally be accessed by service lookup using the factory.name property. + * Each OSGi configuration with factory PID {@code com.adobe.acs.commons.http.impl.HttpClientFactoryImpl} exposes one service of this kind. + * The individual factories will generally be accessed by service lookup using the {@code factory.name} property. + * Despite the name each instance of this interface only holds a single {@link Executor} instance (bound to a single {@code HttpClient}). */ public interface HttpClientFactory { /** - * Get the configured Executor object from this factory. + * Customizes the underlying {@link HttpClientBuilder} which is used to create the singleton http client and executor + * @param builderCustomizer a {@link Consumer} taking the {@link HttpClientBuilder} initialized with the configured basic options. + * @throws IllegalStateException in case {@link #getExecutor()} has been called already + * @since 2.1.0 (Bundle version 6.4.0) + */ + void customize(Consumer builderCustomizer); + + /** + * Get the singleton {@link Executor} object. * - * @return an Executor object + * @return the executor */ Executor getExecutor(); diff --git a/bundle/src/main/java/com/adobe/acs/commons/http/impl/HttpClientFactoryImpl.java b/bundle/src/main/java/com/adobe/acs/commons/http/impl/HttpClientFactoryImpl.java index e807eacebb..e1da15f579 100644 --- a/bundle/src/main/java/com/adobe/acs/commons/http/impl/HttpClientFactoryImpl.java +++ b/bundle/src/main/java/com/adobe/acs/commons/http/impl/HttpClientFactoryImpl.java @@ -26,6 +26,8 @@ import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.apache.http.HttpHost; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.fluent.Executor; import org.apache.http.client.fluent.Request; @@ -39,7 +41,9 @@ import org.apache.sling.commons.osgi.PropertiesUtil; import java.io.IOException; +import java.security.Principal; import java.util.Map; +import java.util.function.Consumer; @Component( label = "ACS AEM Commons - Http Components Fluent Executor Factory", @@ -87,9 +91,12 @@ public class HttpClientFactoryImpl implements HttpClientFactory { @Reference private HttpClientBuilderFactory httpClientBuilderFactory; + private HttpClientBuilder builder; private Executor executor; private String baseUrl; private CloseableHttpClient httpClient; + private HttpHost httpHost; + private Credentials credentials; @Activate protected void activate(Map config) throws Exception { @@ -108,7 +115,7 @@ protected void activate(Map config) throws Exception { int connectTimeout = PropertiesUtil.toInteger(config.get(PROP_CONNECT_TIMEOUT), DEFAULT_CONNECT_TIMEOUT); int soTimeout = PropertiesUtil.toInteger(config.get(PROP_SO_TIMEOUT), DEFAULT_SOCKET_TIMEOUT); - HttpClientBuilder builder = httpClientBuilderFactory.newBuilder(); + builder = httpClientBuilderFactory.newBuilder(); RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout(connectTimeout) @@ -126,15 +133,22 @@ protected void activate(Map config) throws Exception { sslbuilder.build(), NoopHostnameVerifier.INSTANCE); builder.setSSLSocketFactory(sslsf); } - httpClient = builder.build(); - executor = Executor.newInstance(httpClient); - + httpHost = new HttpHost(hostname, port, useSSL ? "https" : "http"); String username = PropertiesUtil.toString(config.get(PROP_USERNAME), null); String password = PropertiesUtil.toString(config.get(PROP_PASSWORD), null); if (username != null && password != null) { - HttpHost httpHost = new HttpHost(hostname, port, useSSL ? "https" : "http"); - executor.auth(httpHost, username, password).authPreemptive(httpHost); + credentials = new UsernamePasswordCredentials(username, password); + } + } + + synchronized Executor createExecutor() { + httpClient = builder.build(); + executor = Executor.newInstance(httpClient); + + if (credentials != null) { + executor.auth(httpHost, credentials).authPreemptive(httpHost); } + return executor; } @Deactivate @@ -148,8 +162,19 @@ protected void deactivate() { } } + @Override + public void customize(Consumer builderCustomizer) { + if (httpClient != null) { + throw new IllegalStateException("The underlying http client has already been created through a call of getExecutor() and can no longer be customized"); + } + builderCustomizer.accept(builder); + } + @Override public Executor getExecutor() { + if (executor == null) { + executor = createExecutor(); + } return executor; } diff --git a/bundle/src/main/java/com/adobe/acs/commons/http/package-info.java b/bundle/src/main/java/com/adobe/acs/commons/http/package-info.java index 236b0f6dcc..e1fee75664 100644 --- a/bundle/src/main/java/com/adobe/acs/commons/http/package-info.java +++ b/bundle/src/main/java/com/adobe/acs/commons/http/package-info.java @@ -18,5 +18,5 @@ /** * Http Injectors. */ -@org.osgi.annotation.versioning.Version("2.0.0") +@org.osgi.annotation.versioning.Version("2.1.0") package com.adobe.acs.commons.http; \ No newline at end of file