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

pl.edu.icm.unity.engine.files.RemoteFileNetworkClient Maven / Gradle / Ivy

/*
 * Copyright (c) 2017 Bixbit - Krzysztof Benedyczak All rights reserved.
 * See LICENCE.txt file for licensing information.
 */
package pl.edu.icm.unity.engine.files;

import static eu.unicore.util.httpclient.HttpClientProperties.CONNECT_TIMEOUT;
import static eu.unicore.util.httpclient.HttpClientProperties.SO_TIMEOUT;

import java.io.IOException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.io.IOUtils;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;

import com.google.common.base.Preconditions;

import eu.emi.security.authn.x509.X509CertChainValidatorExt;
import eu.emi.security.authn.x509.helpers.ssl.SSLTrustManagerWithHostnameChecking;
import eu.emi.security.authn.x509.impl.HostnameMismatchCallback2;
import eu.unicore.util.httpclient.DefaultClientConfiguration;
import eu.unicore.util.httpclient.EmptyHostnameVerifier;
import eu.unicore.util.httpclient.HostnameMismatchCallbackImpl;
import eu.unicore.util.httpclient.HttpClientProperties;
import eu.unicore.util.httpclient.ServerHostnameCheckingMode;
import pl.edu.icm.unity.engine.api.PKIManagement;
import pl.edu.icm.unity.exceptions.EngineException;

/**
 * Wraps configuration of HTTP client which can use custom truststore and makes
 * GET connection, returning the content of downloaded file
 * @author P.Piernik
 *
 */
class RemoteFileNetworkClient
{
	private static final long MAX_BODY_SIZE_TO_LOG = 10240;
	private PKIManagement pkiManagement;

	RemoteFileNetworkClient(PKIManagement pkiManagement)
	{
		this.pkiManagement = pkiManagement;
	}

	ContentsWithType download(URL url, String customTruststore, Duration connectionTimeout, Duration socketReadTimeout, int retriesNumber)
			throws EngineException, IOException
	{
		HttpClient client = new ApacheHttpClientBuilder(pkiManagement)
				.withConnectionTimeout(connectionTimeout)
				.withSocketReadTimeout(socketReadTimeout)
				.withCustomTruststore(customTruststore)
				.withRetriesNumber(retriesNumber)
				.withURL(url)
				.build();
		return download(client, url);
	}

	ContentsWithType download(URL url, String customTruststore) throws EngineException, IOException
	{
		HttpClientProperties properties = new DefaultClientConfiguration().getHttpClientProperties();
		HttpClient client = new ApacheHttpClientBuilder(pkiManagement)
				.withCustomTruststore(customTruststore)
				.withSocketReadTimeout(properties.getIntValue(SO_TIMEOUT))
				.withConnectionTimeout(properties.getIntValue(CONNECT_TIMEOUT))
				.withDefaultRetries()
				.withURL(url)
				.build();
		return download(client, url);
	}
			
	private ContentsWithType download(HttpClient client, URL url) throws EngineException, IOException
	{
		HttpGet request = new HttpGet(url.toString());
		ClassicHttpResponse response = client.executeOpen(null, request, HttpClientContext.create());
		int statusCode = response.getCode();
		if (statusCode != HttpStatus.SC_OK)
		{
			StringBuilder errorMessage = new StringBuilder()
					.append("File download from ")
					.append(url)
					.append(", error: ")
					.append(response.getReasonPhrase());
			if (statusCode != HttpStatus.SC_NOT_FOUND && statusCode != HttpStatus.SC_FORBIDDEN)
			{
				try
				{
					String body = response.getEntity().getContentLength() < MAX_BODY_SIZE_TO_LOG
							? EntityUtils.toString(response.getEntity())
									: "HTTP body too large";
							errorMessage.append(", body: ").append(body);
				}catch(ParseException pe)
				{
					throw new IOException(pe);
				}
			}
			throw new IOException(errorMessage.toString());
		}
		Header contentTypeHeader = response.getFirstHeader("Content-Type");
		String contentType = contentTypeHeader != null ? contentTypeHeader.getValue() : null;
		return new ContentsWithType(IOUtils.toByteArray(response.getEntity().getContent()), contentType);
	}
	
	static class ContentsWithType
	{
		final byte[] contents;
		final String mimeType;

		ContentsWithType(byte[] contents, String mimeType)
		{
			this.contents = contents;
			this.mimeType = mimeType;
		}
	}
	
	private static class ApacheHttpClientBuilder
	{
		private static final int DEFAULT_RETRY_MECHANISM = -1;
		private final PKIManagement pkiManagement;
		
		private URL url;
		private String customTruststore; 
		private Integer connectionTimeout;
		private Integer socketReadTimeout;
		private int retriesNumber = DEFAULT_RETRY_MECHANISM;

		ApacheHttpClientBuilder(PKIManagement pkiManagement)
		{
			this.pkiManagement = pkiManagement;
		}
		
		public ApacheHttpClientBuilder withDefaultRetries()
		{
			this.retriesNumber = DEFAULT_RETRY_MECHANISM;
			return this;
		}

		ApacheHttpClientBuilder withURL(URL url)
		{
			this.url = url;
			return this;
		}
		
		ApacheHttpClientBuilder withCustomTruststore(String customTruststore)
		{
			this.customTruststore = customTruststore;
			return this;
		}
		
		ApacheHttpClientBuilder withConnectionTimeout(int connectionTimeout)
		{
			this.connectionTimeout = connectionTimeout;
			return this;
		}
		
		ApacheHttpClientBuilder withSocketReadTimeout(int socketReadTimeout)
		{
			this.socketReadTimeout = socketReadTimeout;
			return this;
		}

		ApacheHttpClientBuilder withConnectionTimeout(Duration connectionTimeout)
		{
			this.connectionTimeout = (int) connectionTimeout.toMillis();
			return this;
		}

		ApacheHttpClientBuilder withSocketReadTimeout(Duration socketReadTimeout)
		{
			this.socketReadTimeout = (int) socketReadTimeout.toMillis();
			return this;
		}
		
		ApacheHttpClientBuilder withRetriesNumber(int retriesNumber)
		{
			this.retriesNumber = retriesNumber;
			return this;
		}
		
		HttpClient build() throws EngineException
		{
			Preconditions.checkNotNull(url, "url must not provided");
			Preconditions.checkNotNull(connectionTimeout, "connectionTimeout must not provided");
			Preconditions.checkNotNull(socketReadTimeout, "socketReadTimeout must not provided");
			
			HttpClientBuilder builder = HttpClientBuilder.create();
			
			if (retriesNumber == 0)
			{
				builder.disableAutomaticRetries();
				
			} else if (retriesNumber > 0)
			{
				builder.setRetryStrategy(new DefaultHttpRequestRetryStrategy(retriesNumber, TimeValue.ofSeconds(5)));
				
			} else if (retriesNumber == DEFAULT_RETRY_MECHANISM)
			{
				builder.setRetryStrategy(new DefaultHttpRequestRetryStrategy());
			}
			builder.setDefaultRequestConfig(RequestConfig.custom()
					.setConnectionRequestTimeout(Timeout.ofMilliseconds(connectionTimeout))
					.build());
			PoolingHttpClientConnectionManagerBuilder connManagerB = PoolingHttpClientConnectionManagerBuilder.create();
			connManagerB.setDefaultConnectionConfig(ConnectionConfig.custom()
					.setConnectTimeout(Timeout.ofMilliseconds(connectionTimeout))
					.setSocketTimeout(Timeout.ofMilliseconds(socketReadTimeout)).build());
			if (customTruststore != null && url.getProtocol().equals("https"))
			{
				X509CertChainValidatorExt validator = pkiManagement.getValidator(customTruststore);
				SSLContext sslContext = new SSLContextBuilder(validator).build();
				SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new EmptyHostnameVerifier());
				connManagerB.setSSLSocketFactory(sslsf);
				builder.setConnectionManager(connManagerB.build());
			}
			return builder.build();
		}
		
	}
	
	private static class SSLContextBuilder
	{
		private static final String TLSV_1_2 = "TLSv1.2";

		private final X509CertChainValidatorExt validator;
		
		SSLContextBuilder(X509CertChainValidatorExt validator)
		{
			this.validator = validator;
		}

		SSLContext build()
		{
			try
			{
				SSLContext sslContext = SSLContext.getInstance(TLSV_1_2);
				HostnameMismatchCallback2 hostnameVerificationCallback = new HostnameMismatchCallbackImpl(
						ServerHostnameCheckingMode.NONE);
				X509TrustManager tm = new SSLTrustManagerWithHostnameChecking(validator, hostnameVerificationCallback);
				sslContext.init(null, new TrustManager[] { tm }, null);
				return sslContext;

			} catch (NoSuchAlgorithmException | KeyManagementException e)
			{
				throw new IllegalStateException("Could not build SSLContext", e);
			}
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy