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

com.peterphi.std.guice.restclient.resteasy.impl.ResteasyClientFactoryImpl Maven / Gradle / Ivy

There is a newer version: 10.1.5
Show newest version
package com.peterphi.std.guice.restclient.resteasy.impl;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.peterphi.std.annotation.Doc;
import com.peterphi.std.guice.common.shutdown.iface.ShutdownManager;
import com.peterphi.std.guice.common.shutdown.iface.StoppableService;
import com.peterphi.std.guice.restclient.converter.CommonTypesParamConverterProvider;
import com.peterphi.std.threading.Timeout;
import org.apache.http.auth.AuthSchemeProvider;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.impl.NoConnectionReuseStrategy;
import org.apache.http.impl.client.BasicCookieStore;
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.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.conn.SystemDefaultRoutePlanner;
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine;
import org.jboss.resteasy.spi.ResteasyProviderFactory;

import java.net.ProxySelector;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * Builds ResteasyClient objects
 */
@Singleton
public class ResteasyClientFactoryImpl implements StoppableService
{
	private final PoolingHttpClientConnectionManager connectionManager;
	private final ResteasyProviderFactory resteasyProviderFactory;


	@Inject(optional = true)
	@Named("jaxrs.connection.timeout")
	@Doc("The connection timeout for HTTP sockets (default 20s)")
	Timeout connectionTimeout = new Timeout(20, TimeUnit.SECONDS);

	@Inject(optional = true)
	@Named("jaxrs.socket.timeout")
	@Doc("The Socket Timeout for HTTP sockets (default 5m)")
	Timeout socketTimeout = new Timeout(5, TimeUnit.MINUTES);


	@Inject(optional = true)
	@Named("jaxrs.fast-fail.connection.timeout")
	@Doc("The connection timeout for HTTP sockets created for Fast Fail clients (default 15s)")
	Timeout fastFailConnectionTimeout = new Timeout(15, TimeUnit.SECONDS);

	@Inject(optional = true)
	@Named("jaxrs.fast-fail.socket.timeout")
	@Doc("The Socket Timeout for HTTP sockets created for Fast Fail clients (default 15s)")
	Timeout fastFailSocketTimeout = new Timeout(15, TimeUnit.SECONDS);


	@Inject(optional = true)
	@Named("jaxrs.nokeepalive")
	@Doc("If true, keepalive will be disabled for HTTP connections (default true)")
	boolean noKeepalive = true;

	@Inject(optional = true)
	@Named("jaxrs.max-connections-per-route")
	@Doc("The maximum number of connections per HTTP route (default MAXINT)")
	int maxConnectionsPerRoute = Integer.MAX_VALUE;

	@Inject(optional = true)
	@Named("jaxrs.max-total-connections")
	@Doc("The maximum number of HTTP connections in total across all routes (default MAXINT)")
	int maxConnectionsTotal = Integer.MAX_VALUE;

	private ResteasyClient client;


	@Inject
	public ResteasyClientFactoryImpl(final ShutdownManager manager,
	                                 final RemoteExceptionClientResponseFilter remoteExceptionClientResponseFilter,
	                                 final JAXBContextResolver jaxbContextResolver)
	{
		this.resteasyProviderFactory = ResteasyProviderFactory.getInstance();
		resteasyProviderFactory.registerProviderInstance(jaxbContextResolver);

		// Register the joda param converters
		resteasyProviderFactory.registerProviderInstance(new CommonTypesParamConverterProvider());

		// Register the exception processor
		if (remoteExceptionClientResponseFilter != null)
			resteasyProviderFactory.registerProviderInstance(remoteExceptionClientResponseFilter);

		// Set up the Connection Manager
		this.connectionManager = new PoolingHttpClientConnectionManager();
		connectionManager.setDefaultMaxPerRoute(maxConnectionsPerRoute);
		connectionManager.setMaxTotal(maxConnectionsTotal);

		if (manager != null)
			manager.register(this);
	}


	/**
	 * Build a new Resteasy Client, optionally with authentication credentials
	 *
	 * @param fastFail
	 * 		if true, use fast fail timeouts, otherwise false to use default timeouts
	 * @param authScope
	 * 		the auth scope to use - if null then defaults to AuthScope.ANY
	 * @param credentials
	 * 		the credentials to use (optional, e.g. {@link org.apache.http.auth.UsernamePasswordCredentials})
	 * @param customiser
	 * 		optional HttpClientBuilder customiser
	 *
	 * @return
	 */
	public ResteasyClient getOrCreateClient(final boolean fastFail,
	                                        final AuthScope authScope,
	                                        final Credentials credentials,
	                                        final boolean preemptiveAuth,
	                                        final boolean storeCookies,
	                                        Consumer customiser)
	{
		customiser = createHttpClientCustomiser(fastFail, authScope, credentials, preemptiveAuth, storeCookies, customiser);


		return getOrCreateClient(customiser, null);
	}


	/**
	 * N.B. This method signature may change in the future to add new parameters
	 *
	 * @param fastFail
	 * @param authScope
	 * @param credentials
	 * @param preemptiveAuth
	 * @param storeCookies
	 * @param customiser
	 *
	 * @return
	 */
	public Consumer createHttpClientCustomiser(final boolean fastFail,
	                                                              final AuthScope authScope,
	                                                              final Credentials credentials,
	                                                              final boolean preemptiveAuth,
	                                                              final boolean storeCookies,
	                                                              Consumer customiser)
	{
		// Customise timeouts if fast fail mode is enabled
		if (fastFail)
		{
			customiser = concat(customiser, b ->
			{
				RequestConfig.Builder requestBuilder = RequestConfig.custom();

				requestBuilder.setConnectTimeout((int) fastFailConnectionTimeout.getMilliseconds())
				              .setSocketTimeout((int) fastFailSocketTimeout.getMilliseconds());

				b.setDefaultRequestConfig(requestBuilder.build());
			});
		}

		// If credentials were supplied then we should set them up
		if (credentials != null)
		{
			CredentialsProvider credentialsProvider = new BasicCredentialsProvider();

			if (authScope != null)
				credentialsProvider.setCredentials(authScope, credentials);
			else
				credentialsProvider.setCredentials(AuthScope.ANY, credentials);

			// Set up bearer auth scheme provider if we're using bearer credentials
			if (credentials instanceof BearerCredentials)
			{
				customiser = concat(customiser, b ->
				{
					Registry authSchemeRegistry = RegistryBuilder.create().register(
							"Bearer",
							new BearerAuthSchemeProvider()).build();
					b.setDefaultAuthSchemeRegistry(authSchemeRegistry);
				});
			}

			// Set up the credentials customisation
			customiser = concat(customiser, b -> b.setDefaultCredentialsProvider(credentialsProvider));

			if (preemptiveAuth && credentials instanceof BearerCredentials)
				customiser = concat(customiser, b -> b.addInterceptorFirst(new PreemptiveBearerAuthInterceptor()));
			else
				customiser = concat(customiser, b -> b.addInterceptorLast(new PreemptiveBasicAuthInterceptor()));
		}

		// If cookies are enabled then set up a cookie store
		if (storeCookies)
			customiser = concat(customiser, b -> b.setDefaultCookieStore(new BasicCookieStore()));
		return customiser;
	}


	private ResteasyClient getOrCreateClient(Consumer httpCustomiser,
	                                         Consumer resteasyCustomiser)
	{
		if (httpCustomiser == null && resteasyCustomiser == null)
		{
			// Recursively call self to create a shared client for other non-customised consumers
			if (client == null)
				client = getOrCreateClient(b ->
				                           {
					                           // nothing to customise, supplied so we don't take this code path again
				                           }, b ->
				                           {
					                           // nothing to customise, supplied so we don't take this code path again
				                           });

			return client; // use shared client
		}
		else
		{
			final CloseableHttpClient http = createHttpClient(httpCustomiser);


			// Now build a resteasy client
			{
				ResteasyClientBuilder builder = new ResteasyClientBuilder();

				builder.httpEngine(new ApacheHttpClient4Engine(http)).providerFactory(resteasyProviderFactory);

				if (resteasyCustomiser != null)
					resteasyCustomiser.accept(builder);

				return builder.build();
			}
		}
	}


	/**
	 * Build an HttpClient
	 *
	 * @param customiser
	 *
	 * @return
	 */
	public CloseableHttpClient createHttpClient(final Consumer customiser)
	{
		final HttpClientBuilder builder = HttpClientBuilder.create();

		// By default set long call timeouts
		{
			RequestConfig.Builder requestBuilder = RequestConfig.custom();

			requestBuilder.setConnectTimeout((int) connectionTimeout.getMilliseconds())
			              .setSocketTimeout((int) socketTimeout.getMilliseconds());

			builder.setDefaultRequestConfig(requestBuilder.build());
		}

		// Set the default keepalive setting
		if (noKeepalive)
			builder.setConnectionReuseStrategy(new NoConnectionReuseStrategy());

		// By default share the common connection provider
		builder.setConnectionManager(connectionManager);

		// By default use the JRE default route planner for proxies
		builder.setRoutePlanner(new SystemDefaultRoutePlanner(ProxySelector.getDefault()));

		// Allow customisation
		if (customiser != null)
			customiser.accept(builder);

		return builder.build();
	}


	/**
	 * Combine a number of consumers together, ignoring nulls
	 *
	 * @param consumers
	 * @param 
	 *
	 * @return
	 */
	private static  Consumer concat(Consumer... consumers)
	{
		Consumer rootConsumer = null;

		for (Consumer consumer : consumers)
			if (consumer != null)
			{
				if (rootConsumer == null)
					rootConsumer = consumer;
				else
					rootConsumer = rootConsumer.andThen(consumer);
			}

		return rootConsumer;
	}


	@Override
	public void shutdown()
	{
		connectionManager.shutdown();

		if (client != null)
			client.close();
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy