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

se.fortnox.reactivewizard.client.ReactorRxClientProvider Maven / Gradle / Ivy

package se.fortnox.reactivewizard.client;

import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.handler.timeout.ReadTimeoutHandler;
import jakarta.inject.Inject;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;
import reactor.netty.tcp.SslProvider;
import se.fortnox.reactivewizard.metrics.HealthRecorder;

import java.net.InetSocketAddress;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 *
 * Due to the fact that reactors HttpClient is reusable for multiple connections in parallel.
 * this class is responsible for delivering singleton HttpClients connected to different servers (InetSocketAddresses)
 *
 */
public class ReactorRxClientProvider {
    private final ConcurrentHashMap clients = new ConcurrentHashMap<>();
    private final HttpClientConfig                                 config;
    private final HealthRecorder                                   healthRecorder;

    @Inject
    public ReactorRxClientProvider(HttpClientConfig config, HealthRecorder healthRecorder) {
        this.config = config;
        this.healthRecorder = healthRecorder;
    }

    public HttpClient clientFor(InetSocketAddress serverInfo) {
        return clients.computeIfAbsent(serverInfo, this::buildClient);
    }

    private HttpClient setupSsl(HttpClient client, boolean isValidateCertificates) {
        if (!isValidateCertificates) {
            return client.secure(this::configureUnsafeSsl);
        }
        try {
            return client.secure(this::configureSsl);
        } catch (Exception e) {
            throw new IllegalStateException("Unable to create secure https client.", e);
        }
    }

    private HttpClient buildClient(InetSocketAddress socketAddress) {

        ConnectionProvider connectionProvider = ConnectionProvider
            .builder("http-connections")
            .maxConnections(config.getMaxConnections())
            .pendingAcquireMaxCount(-1)
            .maxIdleTime(Duration.of(config.getConnectionMaxIdleTimeInMs(), ChronoUnit.MILLIS))
            .pendingAcquireTimeout(Duration.ofMillis(config.getPoolAcquireTimeoutMs()))
            .build();

        final AtomicInteger errorCount = new AtomicInteger(0);

        HttpClient client = HttpClient
            .create(connectionProvider)
            .tcpConfiguration(tcpClient -> tcpClient
                .doOnConnected(connection -> {
                    connection.addHandler(new ReadTimeoutHandler(config.getReadTimeoutMs(), TimeUnit.MILLISECONDS));
                })
            )
            .port(config.getPort())
            .doOnRequest((httpClientRequest, connection) -> {
                errorCount.set(0);
                healthRecorder.logStatus(connectionProvider, true);
            })
            .doOnError((httpClientRequest, throwable) -> {
                healthRecorder.logStatus(connectionProvider, errorCount.incrementAndGet() <= config.getNumberOfConnectionFailuresAllowed());
            }, (httpClientResponse, throwable) -> { })
            .followRedirect(false);

        if (config.isHttps()) {
            return setupSsl(client, config.isValidateCertificates());
        }

        return client;
    }

    /**
     * Allows for customization of the ssl context.
     */
    void configureSsl(SslProvider.SslContextSpec spec) {
        spec.sslContext(SslContextBuilder.forClient())
            .defaultConfiguration(SslProvider.DefaultConfigurationType.TCP)
            .handshakeTimeoutMillis(30000);
    }

    /**
     * Allows for customization of the unsafe ssl context.
     */
    void configureUnsafeSsl(SslProvider.SslContextSpec spec) {
        spec.sslContext(SslContextBuilder
                .forClient()
                .trustManager(InsecureTrustManagerFactory.INSTANCE)
            )
            .defaultConfiguration(SslProvider.DefaultConfigurationType.TCP)
            .handshakeTimeoutMillis(30000);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy