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

com.github.nhenneaux.resilienthttpclient.monitoredclientpool.SingleIpHttpClient Maven / Gradle / Ivy

package com.github.nhenneaux.resilienthttpclient.monitoredclientpool;

import com.github.nhenneaux.resilienthttpclient.singlehostclient.ServerConfiguration;

import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;

public class SingleIpHttpClient implements AutoCloseable {

    private static final Logger LOGGER = Logger.getLogger(SingleIpHttpClient.class.getSimpleName());

    private final HttpClient httpClient;
    private final InetAddress inetAddress;
    private final URI healthUri;
    private final AtomicBoolean healthy;
    private final Future scheduledFuture;
    private final ServerConfiguration serverConfiguration;

    /**
     * Create a new instance of the client and schedule a task to refresh is healthiness.
     *
     * @param httpClient               the underlying HTTP client
     * @param inetAddress              the target IP address
     * @param serverConfiguration      the configuration of the server
     * @param scheduledExecutorService the scheduled executor service to schedule the refresh.
     */
    public SingleIpHttpClient(
            HttpClient httpClient,
            InetAddress inetAddress,
            ServerConfiguration serverConfiguration,
            ScheduledExecutorService scheduledExecutorService
    ) {
        Objects.requireNonNull(serverConfiguration);
        this.httpClient = Objects.requireNonNull(httpClient);
        this.inetAddress = Objects.requireNonNull(inetAddress);
        this.healthUri = healthUri(inetAddress, serverConfiguration);

        this.serverConfiguration = serverConfiguration;
        this.healthy = new AtomicBoolean();

        final long connectionHealthCheckPeriodInSeconds = serverConfiguration.getConnectionHealthCheckPeriodInSeconds();
        this.scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(
                this::checkHealthStatus,
                0L,
                connectionHealthCheckPeriodInSeconds,
                TimeUnit.SECONDS
        );
    }

    /**
     * Create a new instance and check its health once.
     *
     * @param httpClient          the underlying HTTP client
     * @param inetAddress         the target IP address
     * @param serverConfiguration the configuration of the server
     */
    public SingleIpHttpClient(
            HttpClient httpClient,
            InetAddress inetAddress,
            ServerConfiguration serverConfiguration
    ) {
        Objects.requireNonNull(serverConfiguration);
        this.httpClient = Objects.requireNonNull(httpClient);
        this.inetAddress = Objects.requireNonNull(inetAddress);
        this.healthUri = healthUri(inetAddress, serverConfiguration);

        this.serverConfiguration = serverConfiguration;
        this.healthy = new AtomicBoolean();
        this.scheduledFuture = CompletableFuture.completedFuture(null);
        checkHealthStatus();
    }

    private URI healthUri(InetAddress inetAddress, ServerConfiguration serverConfiguration) {
        try {
            return new URL("https", inetAddress.getHostAddress(), serverConfiguration.getPort(), serverConfiguration.getHealthPath()).toURI();
        } catch (URISyntaxException | MalformedURLException e) {
            throw new IllegalArgumentException("Cannot build health URI from " + serverConfiguration, e);
        }
    }

    /**
     * If called and the previous health status was unhealthy, then a new health check is performed.
     */
    public boolean isHealthy() {
        if(!healthy.get()) {
            checkHealthStatus();
        }
        return healthy.get();
    }

    /**
     * Determine whether this client is able to reach the given IP address through HTTP protocol and get a valid HTTP response, i.e. with status between 200 and 499.
     */
    void checkHealthStatus() {
        final long start = System.nanoTime();
        try {
            final HttpRequest.Builder httpRequestBuilder = HttpRequest.newBuilder().uri(healthUri);
            if (serverConfiguration.getReadTimeoutInMilliseconds() >= 0) {
                httpRequestBuilder.timeout(Duration.ofMillis(serverConfiguration.getReadTimeoutInMilliseconds()));
            }
            final int statusCode = httpClient.sendAsync(httpRequestBuilder
                            .build(),
                    HttpResponse.BodyHandlers.ofString())
                    .thenApply(HttpResponse::statusCode)
                    .join();

            LOGGER.log(Level.INFO, () -> "Checked health for URI " + healthUri + ", status is `" + statusCode + "`" + timingLogStatement(start));
            healthy.set(statusCode >= 200 && statusCode <= 499);
        } catch (RuntimeException e) {
            LOGGER.log(Level.WARNING, "Failed to check health for address " + healthUri + ", error is `" + e + "`" + timingLogStatement(start), e);
            healthy.set(false);
        }
    }

    private String timingLogStatement(long start) {
        return " in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start) + "ms.";
    }

    public InetAddress getInetAddress() {
        return inetAddress;
    }

    public HttpClient getHttpClient() {
        return httpClient;
    }

    @Override
    public String toString() {
        return "SingleIpHttpClient{" +
                "inetAddress=" + inetAddress +
                ", healthUri=" + healthUri +
                '}';
    }

    @Override
    public void close() {
        scheduledFuture.cancel(true);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy