Skip to content

http client config hooks interface #59

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## 3.1.3 (08/11/2021)
- [Issue-55](https://github.com/SourceLabOrg/kafka-connect-client/issues/55) Create new HttpContext for every request.
- [PR-59](https://github.com/SourceLabOrg/kafka-connect-client/pull/59) Adds supportted way to modify the underlying configuration of HttpClient via HttpClientConfigHooks interface.

Usage of these hooks would look like:

```java
// Directly create underlying RestClient and pass your HttpClientConfigHooks implementation.
final RestClient restClient = new HttpClientRestClient(new HttpClientConfigHooks {
// Override methods as needed to modify behavior.
});

// Create KafkaConnectClient, passing configuration and RestClient implementation
final KafkaConnectClient client = new KafkaConnectClient(configuration, restClient);

// Use client as normal...
```

## 3.1.2 (07/21/2021)

- [Issue-54](https://github.com/SourceLabOrg/kafka-connect-client/issues/54) Resolution for issue-54
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Copyright 2018, 2019, 2020, 2021 SourceLab.org https://github.com/SourceLabOrg/kafka-connect-client
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package org.sourcelab.kafka.connect.apiclient.rest;

/**
* Default implementation makes no modifications.
*/
public class DefaultHttpClientConfigHooks implements HttpClientConfigHooks {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/**
* Copyright 2018, 2019, 2020, 2021 SourceLab.org https://github.com/SourceLabOrg/kafka-connect-client
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package org.sourcelab.kafka.connect.apiclient.rest;

import org.apache.http.client.AuthCache;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClientBuilder;
import org.sourcelab.kafka.connect.apiclient.Configuration;

/**
* HttpClient configuration hooks.
*
* Provides an interface for modifying how the underlying HttpClient instance is created.
*
* Usage of this would look like:
*
* final RestClient restClient = new HttpClientRestClient(new HttpClientConfigHooks {
* // Override methods as needed to modify behavior.
* });
*
* // Create client, passing configuration and RestClient implementation
* final KafkaConnectClient client = new KafkaConnectClient(configuration, restClient);
*
* // Use client as normal...
*
*/
public interface HttpClientConfigHooks {
/**
* Create HttpClientBuilder instance.
* @param configuration KafkaConnectClient configuration.
* @return HttpClientBuilder instance.
*/
default HttpClientBuilder createHttpClientBuilder(final Configuration configuration) {
return HttpClientBuilder.create();
}

/**
* Create HttpsContextBuilder instance.
* @param configuration KafkaConnectClient configuration.
* @return HttpsContextBuilder instance.
*/
default HttpsContextBuilder createHttpsContextBuilder(final Configuration configuration) {
return new HttpsContextBuilder(configuration);
}

/**
* Create RequestConfig.Builder instance.
* @param configuration KafkaConnectClient configuration.
* @return RequestConfig.Builder instance.
*/
default RequestConfig.Builder createRequestConfigBuilder(final Configuration configuration) {
return RequestConfig.custom();
}

/**
* Create AuthCache instance.
* @param configuration KafkaConnectClient configuration.
* @return AuthCache instance.
*/
default AuthCache createAuthCache(final Configuration configuration) {
return new BasicAuthCache();
}

/**
* Create CredentialsProvider instance.
* @param configuration KafkaConnectClient configuration.
* @return CredentialsProvider instance.
*/
default CredentialsProvider createCredentialsProvider(final Configuration configuration) {
return new BasicCredentialsProvider();
}

/**
* Create HttpClientContext instance.
* @param configuration KafkaConnectClient configuration.
* @return HttpClientContext instance.
*/
default HttpClientContext createHttpClientContext(final Configuration configuration) {
return HttpClientContext.create();
}

/**
* Ability to modify or replace the AuthCache instance after initial configuration has been performed on it.
* @param configuration KafkaConnectClient configuration.
* @return AuthCache instance.
*/
default AuthCache modifyAuthCache(final Configuration configuration, final AuthCache authCache) {
return authCache;
}

/**
* Ability to modify or replace the CredentialsProvider instance after initial configuration has been performed on it.
* @param configuration KafkaConnectClient configuration.
* @return CredentialsProvider instance.
*/
default CredentialsProvider modifyCredentialsProvider(final Configuration configuration, final CredentialsProvider credentialsProvider) {
return credentialsProvider;
}

/**
* Ability to modify or replace the RequestConfig.Builder instance after initial configuration has been performed on it.
* @param configuration KafkaConnectClient configuration.
* @return RequestConfig.Builder instance.
*/
default RequestConfig.Builder modifyRequestConfig(final Configuration configuration, final RequestConfig.Builder builder) {
return builder;
}

/**
* Ability to modify or replace the HttpClientBuilder instance after initial configuration has been performed on it.
* @param configuration KafkaConnectClient configuration.
* @return HttpClientBuilder instance.
*/
default HttpClientBuilder modifyHttpClientBuilder(final Configuration configuration, final HttpClientBuilder builder) {
return builder;
}

/**
* Ability to modify or replace the HttpClientContext instance after initial configuration has been performed on it.
* @param configuration KafkaConnectClient configuration.
* @return HttpClientContext instance.
*/
default HttpClientContext modifyHttpClientContext(final Configuration configuration, final HttpClientContext context) {
return context;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicHeader;
Expand All @@ -60,6 +58,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
Expand Down Expand Up @@ -96,10 +95,24 @@ public class HttpClientRestClient implements RestClient {
*/
private CredentialsProvider credsProvider;

/**
* Provides an interface for modifying how the underlying HttpClient instance is created.
*/
private final HttpClientConfigHooks configHooks;

/**
* Constructor.
*/
public HttpClientRestClient() {
this(new DefaultHttpClientConfigHooks());
}

/**
* Constructor allowing for injecting configuration hooks.
* @param configHooks For hooking/overriding into how the underlying HttpClient is configured.
*/
public HttpClientRestClient(final HttpClientConfigHooks configHooks) {
this.configHooks = configHooks;
}

/**
Expand All @@ -113,10 +126,13 @@ public void init(final Configuration configuration) {
this.configuration = configuration;

// Create https context builder utility.
final HttpsContextBuilder httpsContextBuilder = new HttpsContextBuilder(configuration);
final HttpsContextBuilder httpsContextBuilder = configHooks.createHttpsContextBuilder(configuration);

// Setup client builder
final HttpClientBuilder clientBuilder = createHttpClientBuilder();
// Create and setup client builder
HttpClientBuilder clientBuilder = Objects.requireNonNull(
configHooks.createHttpClientBuilder(configuration),
"HttpClientConfigHook::createHttpClientBuilder() must return non-null instance."
);
clientBuilder
// Define timeout
.setConnectionTimeToLive(configuration.getConnectionTimeToLiveInSeconds(), TimeUnit.SECONDS)
Expand All @@ -125,15 +141,24 @@ public void init(final Configuration configuration) {
.setSSLSocketFactory(httpsContextBuilder.createSslSocketFactory());

// Define our RequestConfigBuilder
final RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
RequestConfig.Builder requestConfigBuilder = Objects.requireNonNull(
configHooks.createRequestConfigBuilder(configuration),
"HttpClientConfigHook::createRequestConfigBuilder() must return non-null instance."
);

requestConfigBuilder.setConnectTimeout(configuration.getRequestTimeoutInSeconds() * 1_000);

// Define our Credentials Provider
credsProvider = new BasicCredentialsProvider();
credsProvider = Objects.requireNonNull(
configHooks.createCredentialsProvider(configuration),
"HttpClientConfigHook::createCredentialsProvider() must return non-null instance."
);

// Define our auth cache
authCache = new BasicAuthCache();
authCache = Objects.requireNonNull(
configHooks.createAuthCache(configuration),
"HttpClientConfigHook::createAuthCache() must return non-null instance."
);

// If we have a configured proxy host
if (configuration.getProxyHost() != null) {
Expand Down Expand Up @@ -186,13 +211,31 @@ public void init(final Configuration configuration) {
}
}

// Call Modify hooks
authCache = Objects.requireNonNull(
configHooks.modifyAuthCache(configuration, authCache),
"HttpClientConfigHook::modifyAuthCache() must return non-null instance."
);
credsProvider = Objects.requireNonNull(
configHooks.modifyCredentialsProvider(configuration, credsProvider),
"HttpClientConfigHook::modifyCredentialsProvider() must return non-null instance."
);
requestConfigBuilder = Objects.requireNonNull(
configHooks.modifyRequestConfig(configuration, requestConfigBuilder),
"HttpClientConfigHook::modifyRequestConfig() must return non-null instance."
);

// Attach Credentials provider to client builder.
clientBuilder.setDefaultCredentialsProvider(credsProvider);

// Attach default request config
clientBuilder.setDefaultRequestConfig(requestConfigBuilder.build());

// build http client
clientBuilder = Objects.requireNonNull(
configHooks.modifyHttpClientBuilder(configuration, clientBuilder),
"HttpClientConfigHook::modifyHttpClientBuilder() must return non-null instance."
);
httpClient = clientBuilder.build();
}

Expand Down Expand Up @@ -407,12 +450,12 @@ private String constructApiUrl(final String endPoint) {
*/
private HttpClientContext createHttpClientContext() {
// Define our context
final HttpClientContext httpClientContext = HttpClientContext.create();
final HttpClientContext httpClientContext = configHooks.createHttpClientContext(configuration);

// Configure context.
httpClientContext.setAuthCache(authCache);
httpClientContext.setCredentialsProvider(credsProvider);

return httpClientContext;
return configHooks.modifyHttpClientContext(configuration, httpClientContext);
}
}
Loading