de.captaingoldfish.scim.sdk.client.http.ScimHttpClient Maven / Gradle / Ivy
// Generated by delombok at Sat Oct 07 17:30:34 CEST 2023
package de.captaingoldfish.scim.sdk.client.http;
import java.io.Closeable;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import de.captaingoldfish.scim.sdk.client.ScimClientConfig;
import de.captaingoldfish.scim.sdk.client.exceptions.ConnectTimeoutRuntimeException;
import de.captaingoldfish.scim.sdk.client.exceptions.IORuntimeException;
import de.captaingoldfish.scim.sdk.client.exceptions.SSLHandshakeRuntimeException;
import de.captaingoldfish.scim.sdk.client.exceptions.SocketTimeoutRuntimeException;
import de.captaingoldfish.scim.sdk.client.exceptions.UnknownHostRuntimeException;
import de.captaingoldfish.scim.sdk.common.constants.HttpHeader;
// @formatter:off
// @formatter:on
/**
* author: Pascal Knueppel
* created at: 09.12.2019 - 12:26
*
* will provide a service for creating pre-configured apache {@link CloseableHttpClient}s
* the configuration added to the created clients are the following
*
* - proxy authentication for a configured proxy (see {@link ProxyHelper})
* - a pre-configured {@link SSLContext}. Depends on the specific declarated bean provided by the
* developer
*
*/
public class ScimHttpClient implements Closeable
{
@java.lang.SuppressWarnings("all")
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ScimHttpClient.class);
/**
* Number by which timeout variables are multiplied.
*/
private static final int TIMEOUT_MILLIS = 1000;
/**
* reads the timeout in seconds for setting up a connection
* default: 3
*/
private ScimClientConfig scimClientConfig;
/**
* used to send requests to the server
*/
private CloseableHttpClient httpClient;
public ScimHttpClient(ScimClientConfig scimClientConfig)
{
this.scimClientConfig = scimClientConfig;
}
/**
* translates an apache {@link CloseableHttpResponse} to an {@link HttpResponse} object
*
* @param response the apache http response
* @return the {@link HttpResponse} representation
* @throws IOException if the inputstream of the response body could not be read
*/
private static HttpResponse toResponse(CloseableHttpResponse response) throws IOException
{
Map headers = new HashMap<>();
Arrays.stream(response.getAllHeaders()).forEach(header -> headers.put(header.getName(), header.getValue()));
return HttpResponse.builder()
.httpStatusCode(response.getStatusLine().getStatusCode())
.responseBody(response.getEntity() == null ? null
: IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8))
.responseHeaders(headers)
.build();
}
/**
* this method generates a http-client instance and will set the ssl-context
*
* @return a http-client instance
*/
public CloseableHttpClient getHttpClient()
{
if (httpClient != null)
{
return httpClient;
}
HttpClientBuilder clientBuilder = HttpClientBuilder.create();
CredentialsProvider credentialsProvider = null;
if (scimClientConfig.getProxy() != null && scimClientConfig.getProxy().isProxySet())
{
credentialsProvider = scimClientConfig.getProxy().getProxyCredentials();
}
clientBuilder.setDefaultCredentialsProvider(credentialsProvider);
if (scimClientConfig.getClientAuth() != null || scimClientConfig.getTruststore() != null)
{
clientBuilder.setSSLContext(SSLContextHelper.getSslContext(scimClientConfig.getClientAuth(),
scimClientConfig.getTruststore(),
scimClientConfig.getTlsVersion()));
}
clientBuilder.setConnectionReuseStrategy((response, context) -> false);
if (scimClientConfig.getHostnameVerifier() != null)
{
clientBuilder.setSSLHostnameVerifier(scimClientConfig.getHostnameVerifier());
}
clientBuilder.setDefaultRequestConfig(getRequestConfig());
if (!scimClientConfig.isEnableCookieManagement())
{
clientBuilder.disableCookieManagement();
}
if (scimClientConfig.getConfigManipulator() != null)
{
scimClientConfig.getConfigManipulator().modifyHttpClientConfig(clientBuilder);
}
httpClient = clientBuilder.build();
return httpClient;
}
/**
* will configure the apache http-client
*
* @return the original request with an extended configuration
*/
public RequestConfig getRequestConfig()
{
RequestConfig.Builder configBuilder;
if (scimClientConfig.getProxy() == null)
{
configBuilder = RequestConfig.copy(RequestConfig.DEFAULT);
}
else
{
RequestConfig proxyConfig = scimClientConfig.getProxy().getProxyConfig();
configBuilder = RequestConfig.copy(proxyConfig);
}
if (scimClientConfig.getConnectTimeout() > 0)
{
configBuilder.setConnectTimeout(scimClientConfig.getConnectTimeout() * TIMEOUT_MILLIS);
log.debug("Connection timeout \'{}\' seconds", scimClientConfig.getConnectTimeout());
}
if (scimClientConfig.getSocketTimeout() > 0)
{
configBuilder.setSocketTimeout(scimClientConfig.getSocketTimeout() * TIMEOUT_MILLIS);
log.debug("Socket timeout \'{}\' seconds", scimClientConfig.getSocketTimeout());
}
if (scimClientConfig.getRequestTimeout() > 0)
{
configBuilder.setConnectionRequestTimeout(scimClientConfig.getRequestTimeout() * TIMEOUT_MILLIS);
log.debug("Request timeout \'{}\' seconds", scimClientConfig.getRequestTimeout());
}
if (scimClientConfig.getConfigManipulator() != null)
{
scimClientConfig.getConfigManipulator().modifyRequestConfig(configBuilder);
}
return configBuilder.build();
}
/**
* this method will send the request with the apache http client and will also handle {@link IOException}s and
* wrap them into {@link IORuntimeException}s
*
* @param uriRequest the request that should be send
* @return the response of the server
* @throws SSLHandshakeRuntimeException in case that the server represented a certificate that is not trusted
* @throws ConnectRuntimeException happens if the server does not respond (wrong URL wrong port or something
* else)
* @throws ConnectTimeoutRuntimeException if the server took too long to establish a connection
* @throws SocketTimeoutRuntimeException if the server took too long to send the response
* @throws IORuntimeException base exception that represents all previous mentioned exceptions
*/
public HttpResponse sendRequest(HttpUriRequest uriRequest)
{
if (httpClient == null)
{
httpClient = getHttpClient();
}
setAuthenticationIfMissing(uriRequest);
if (log.isTraceEnabled())
{
log.trace("Sending http request: \n\tmethod: {}\n\turi: {}",
uriRequest.getMethod(),
uriRequest.getURI().toString());
}
try (CloseableHttpResponse response = httpClient.execute(uriRequest))
{
return toResponse(response);
}
catch (SSLHandshakeException ex)
{
throw new SSLHandshakeRuntimeException("handshake error during connection setup", ex);
}
catch (ConnectTimeoutException ex)
{
throw new ConnectTimeoutRuntimeException("connection timeout after \'" + scimClientConfig.getConnectTimeout()
+ "\' seconds", ex);
}
catch (SocketTimeoutException ex)
{
throw new SocketTimeoutRuntimeException("socket timeout after \'" + scimClientConfig.getSocketTimeout()
+ "\' seconds", ex);
}
catch (UnknownHostException ex)
{
throw new UnknownHostRuntimeException("could not find host \'" + uriRequest.getURI().getHost() + "\'", ex);
}
catch (IOException ex)
{
throw new IORuntimeException("communication with server failed", ex);
}
}
/**
* adds basic authentication to the request if it was configured and no "Authorization" header has been set
* yet
*/
private void setAuthenticationIfMissing(HttpUriRequest uriRequest)
{
Optional.ofNullable(scimClientConfig.getBasicAuth()).ifPresent(basicAuth -> {
if (null == uriRequest.getFirstHeader(HttpHeader.AUTHORIZATION))
{
uriRequest.addHeader(HttpHeader.AUTHORIZATION, scimClientConfig.getBasicAuth().getAuthorizationHeaderValue());
}
});
}
/**
* will close the apache http client
*/
@Override
public void close()
{
if (httpClient == null)
{
return;
}
try
{
httpClient.close();
}
catch (IOException e)
{
// will never happen since the implementation of httpclient is an InternalHttpClient from apache
log.error(e.getMessage(), e);
}
httpClient = null;
}
/**
* Number by which timeout variables are multiplied.
*/
@java.lang.SuppressWarnings("all")
public static int getTIMEOUT_MILLIS()
{
return ScimHttpClient.TIMEOUT_MILLIS;
}
/**
* reads the timeout in seconds for setting up a connection
* default: 3
*/
@java.lang.SuppressWarnings("all")
public ScimClientConfig getScimClientConfig()
{
return this.scimClientConfig;
}
}