All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sourcelab.kafka.connect.apiclient.rest.HttpClientRestClient Maven / Gradle / Ivy

The newest version!
/**
 * 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.Header;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
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.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicHeader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sourcelab.kafka.connect.apiclient.Configuration;
import org.sourcelab.kafka.connect.apiclient.request.JacksonFactory;
import org.sourcelab.kafka.connect.apiclient.request.Request;
import org.sourcelab.kafka.connect.apiclient.rest.exceptions.ConnectionException;
import org.sourcelab.kafka.connect.apiclient.rest.exceptions.ResultParsingException;
import org.sourcelab.kafka.connect.apiclient.rest.handlers.RestResponseHandler;

import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.SocketException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * RestClient implementation using HTTPClient.
 */
public class HttpClientRestClient implements RestClient {
    private static final Logger logger = LoggerFactory.getLogger(HttpClientRestClient.class);

    /**
     * Default headers included with every request.
     */
    private static final Collection
DEFAULT_HEADERS = Collections.unmodifiableCollection(Arrays.asList( new BasicHeader("Accept", "application/json"), new BasicHeader("Content-Type", "application/json") )); /** * Save a copy of the configuration. */ private Configuration configuration; /** * Our underlying Http Client. */ private CloseableHttpClient httpClient; /** * The AuthCache used when creating the HttpClientContext. */ private AuthCache authCache; /** * The CredentialsProvider used when creating the HttpClientContext. */ 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; } /** * Initialization method. This takes in the configuration and sets up the underlying * http client appropriately. * @param configuration The user defined configuration. */ @Override public void init(final Configuration configuration) { // Save reference to configuration this.configuration = configuration; // Create https context builder utility. final HttpsContextBuilder httpsContextBuilder = configHooks.createHttpsContextBuilder(configuration); // 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) // Define SSL Socket Factory instance. .setSSLSocketFactory(httpsContextBuilder.createSslSocketFactory()); // Define our RequestConfigBuilder 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 = Objects.requireNonNull( configHooks.createCredentialsProvider(configuration), "HttpClientConfigHook::createCredentialsProvider() must return non-null instance." ); // Define our auth cache authCache = Objects.requireNonNull( configHooks.createAuthCache(configuration), "HttpClientConfigHook::createAuthCache() must return non-null instance." ); // If we have a configured proxy host if (configuration.getProxyHost() != null) { // Define proxy host final HttpHost proxyHost = new HttpHost( configuration.getProxyHost(), configuration.getProxyPort(), configuration.getProxyScheme() ); // If we have proxy auth enabled if (configuration.getProxyUsername() != null) { // Add proxy credentials credsProvider.setCredentials( new AuthScope(configuration.getProxyHost(), configuration.getProxyPort()), new UsernamePasswordCredentials(configuration.getProxyUsername(), configuration.getProxyPassword()) ); // Preemptive load context with authentication. authCache.put( new HttpHost(configuration.getProxyHost(), configuration.getProxyPort(), configuration.getProxyScheme()), new BasicScheme() ); } // Attach Proxy to request config builder requestConfigBuilder.setProxy(proxyHost); } // If BasicAuth credentials are configured. if (configuration.getBasicAuthUsername() != null) { try { // parse ApiHost for Hostname and port. final URL apiUrl = new URL(configuration.getApiHost()); // Add Kafka-Connect credentials credsProvider.setCredentials( new AuthScope(apiUrl.getHost(), apiUrl.getPort()), new UsernamePasswordCredentials( configuration.getBasicAuthUsername(), configuration.getBasicAuthPassword() ) ); // Preemptive load context with authentication. authCache.put( new HttpHost(apiUrl.getHost(), apiUrl.getPort(), apiUrl.getProtocol()), new BasicScheme() ); } catch (final MalformedURLException exception) { throw new RuntimeException(exception.getMessage(), exception); } } // 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(); } @Override public void close() { if (httpClient != null) { try { httpClient.close(); } catch (final IOException e) { logger.error("Error closing: {}", e.getMessage(), e); } } httpClient = null; } /** * Create the HttpClientBuilder which is used to create the HttpClient. * This method allows users to extend this class and use a custom builder if needed. * @return The HttpClientBuilder to use for creating the HttpClient. */ protected HttpClientBuilder createHttpClientBuilder() { return HttpClientBuilder.create(); } /** * Make a request against the Pardot API. * @param request The request to submit. * @return The response, in UTF-8 String format. * @throws RestException if something goes wrong. */ @Override public RestResponse submitRequest(final Request request) throws RestException { final String url = constructApiUrl(request.getApiEndpoint()); final ResponseHandler responseHandler = new RestResponseHandler(); try { switch (request.getRequestMethod()) { case GET: return submitGetRequest(url, Collections.emptyMap(), responseHandler); case POST: return submitPostRequest(url, request.getRequestBody(), responseHandler); case PUT: return submitPutRequest(url, request.getRequestBody(), responseHandler); case DELETE: return submitDeleteRequest(url, request.getRequestBody(), responseHandler); default: throw new IllegalArgumentException("Unknown Request Method: " + request.getRequestMethod()); } } catch (final IOException exception) { throw new RestException(exception.getMessage(), exception); } } /** * Internal GET method. * @param url Url to GET to. * @param getParams GET parameters to include in the request * @param responseHandler The response Handler to use to parse the response * @param The type that ResponseHandler returns. * @return Parsed response. */ private T submitGetRequest(final String url, final Map getParams, final ResponseHandler responseHandler) throws IOException { try { // Construct URI including our request parameters. final URIBuilder uriBuilder = new URIBuilder(url) .setCharset(StandardCharsets.UTF_8); // Attach submitRequest params for (final Map.Entry entry : getParams.entrySet()) { uriBuilder.setParameter(entry.getKey(), entry.getValue()); } // Build Get Request final HttpGet get = new HttpGet(uriBuilder.build()); // Add default headers. DEFAULT_HEADERS.forEach(get::addHeader); logger.debug("Executing request {}", get.getRequestLine()); // Execute and return return execute(get, responseHandler); } catch (final ClientProtocolException | SocketException | URISyntaxException | SSLHandshakeException connectionException) { // Typically this is a connection or certificate issue. throw new ConnectionException(connectionException.getMessage(), connectionException); } catch (final IOException ioException) { // Typically this is a parse error. throw new ResultParsingException(ioException.getMessage(), ioException); } } /** * Internal POST method. * @param url Url to POST to. * @param requestBody POST entity include in the request body * @param responseHandler The response Handler to use to parse the response * @param The type that ResponseHandler returns. * @return Parsed response. */ private T submitPostRequest(final String url, final Object requestBody, final ResponseHandler responseHandler) throws IOException { try { final HttpPost post = new HttpPost(url); // Add default headers. DEFAULT_HEADERS.forEach(post::addHeader); // Convert to Json final String jsonPayloadStr = JacksonFactory.newInstance().writeValueAsString(requestBody); post.setEntity(new StringEntity(jsonPayloadStr)); logger.debug("Executing request {} with {}", post.getRequestLine(), jsonPayloadStr); // Execute and return return execute(post, responseHandler); } catch (final ClientProtocolException | SocketException | SSLHandshakeException connectionException) { // Typically this is a connection issue. throw new ConnectionException(connectionException.getMessage(), connectionException); } catch (final IOException ioException) { // Typically this is a parse error. throw new ResultParsingException(ioException.getMessage(), ioException); } } /** * Internal PUT method. * @param url Url to POST to. * @param requestBody POST entity include in the request body * @param responseHandler The response Handler to use to parse the response * @param The type that ResponseHandler returns. * @return Parsed response. */ private T submitPutRequest(final String url, final Object requestBody, final ResponseHandler responseHandler) throws IOException { try { final HttpPut put = new HttpPut(url); // Add default headers. DEFAULT_HEADERS.forEach(put::addHeader); // Convert to Json and submit as payload. final String jsonPayloadStr = JacksonFactory.newInstance().writeValueAsString(requestBody); put.setEntity(new StringEntity(jsonPayloadStr)); logger.debug("Executing request {} with {}", put.getRequestLine(), jsonPayloadStr); // Execute and return return execute(put, responseHandler); } catch (final ClientProtocolException | SocketException | SSLHandshakeException connectionException) { // Typically this is a connection issue. throw new ConnectionException(connectionException.getMessage(), connectionException); } catch (final IOException ioException) { // Typically this is a parse error. throw new ResultParsingException(ioException.getMessage(), ioException); } } /** * Internal DELETE method. * @param url Url to DELETE to. * @param requestBody POST entity include in the request body * @param responseHandler The response Handler to use to parse the response * @param The type that ResponseHandler returns. * @return Parsed response. */ private T submitDeleteRequest(final String url, final Object requestBody, final ResponseHandler responseHandler) throws IOException { try { final HttpDelete delete = new HttpDelete(url); // Add default headers. DEFAULT_HEADERS.forEach(delete::addHeader); // Convert to Json final String jsonPayloadStr = JacksonFactory.newInstance().writeValueAsString(requestBody); logger.debug("Executing request {} with {}", delete.getRequestLine(), jsonPayloadStr); // Execute and return return execute(delete, responseHandler); } catch (final ClientProtocolException | SocketException | SSLHandshakeException connectionException) { // Typically this is a connection issue. throw new ConnectionException(connectionException.getMessage(), connectionException); } catch (final IOException ioException) { // Typically this is a parse error. throw new ResultParsingException(ioException.getMessage(), ioException); } } /** * Creates an HttpClientContext and executes the HTTP request. * * @param request The request to execute * @param responseHandler The response Handler to use to parse the response * @param The type that ResponseHandler returns. * @return Parsed response. */ private T execute(final HttpUriRequest request, final ResponseHandler responseHandler) throws IOException { return httpClient.execute(request, responseHandler, createHttpClientContext()); } /** * Internal helper method for generating URLs w/ the appropriate API host and API version. * @param endPoint The end point you want to hit. * @return Constructed URL for the end point. */ private String constructApiUrl(final String endPoint) { return configuration.getApiHost() + endPoint; } /** * Creates a new HttpClientContext with the authCache and credsProvider. * @return the created HttpClientContext. */ private HttpClientContext createHttpClientContext() { // Define our context final HttpClientContext httpClientContext = configHooks.createHttpClientContext(configuration); // Configure context. httpClientContext.setAuthCache(authCache); httpClientContext.setCredentialsProvider(credsProvider); return configHooks.modifyHttpClientContext(configuration, httpClientContext); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy