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

org.jboss.resteasy.reactive.client.impl.ClientBuilderImpl Maven / Gradle / Ivy

There is a newer version: 3.17.5
Show newest version
package org.jboss.resteasy.reactive.client.impl;

import static org.jboss.resteasy.reactive.client.api.QuarkusRestClientProperties.CONNECT_TIMEOUT;
import static org.jboss.resteasy.reactive.client.api.QuarkusRestClientProperties.READ_TIMEOUT;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;

import jakarta.ws.rs.RuntimeType;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.core.Configuration;
import jakarta.ws.rs.ext.MessageBodyReader;
import jakarta.ws.rs.ext.MessageBodyWriter;

import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.client.TlsConfig;
import org.jboss.resteasy.reactive.client.api.ClientLogger;
import org.jboss.resteasy.reactive.client.api.LoggingScope;
import org.jboss.resteasy.reactive.client.interceptors.ClientGZIPDecodingInterceptor;
import org.jboss.resteasy.reactive.client.logging.DefaultClientLogger;
import org.jboss.resteasy.reactive.client.spi.ClientContextResolver;
import org.jboss.resteasy.reactive.common.jaxrs.ConfigurationImpl;
import org.jboss.resteasy.reactive.common.jaxrs.MultiQueryParamMode;

import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClientOptions;
import io.vertx.core.http.HttpVersion;
import io.vertx.core.net.JksOptions;
import io.vertx.core.net.ProxyOptions;
import io.vertx.core.net.SSLOptions;

public class ClientBuilderImpl extends ClientBuilder {

    private static final Logger log = Logger.getLogger(ClientBuilderImpl.class);

    private static final ClientContextResolver CLIENT_CONTEXT_RESOLVER = ClientContextResolver.getInstance();
    private static final char[] EMPTY_CHAR_ARRAY = new char[0];
    public static final String PIPE = Pattern.quote("|");

    private ConfigurationImpl configuration;
    private String proxyHost;
    private int proxyPort;
    private String proxyPassword;
    private String proxyUser;
    private String nonProxyHosts;

    private boolean followRedirects;

    private boolean http2;
    private boolean alpn;

    // security settings
    private KeyStore keyStore;
    private char[] keystorePassword;
    private KeyStore trustStore;
    private char[] trustStorePassword;
    private boolean trustAll;
    private boolean verifyHost = true;
    // overridden security settings
    private TlsConfig tlsConfig;

    private LoggingScope loggingScope;
    private Integer loggingBodySize = 100;

    private int maxChunkSize = 8096;
    private MultiQueryParamMode multiQueryParamMode;

    private ClientLogger clientLogger = new DefaultClientLogger();
    private String userAgent = RestClientRequestContext.DEFAULT_USER_AGENT_VALUE;

    private boolean enableCompression;

    public ClientBuilderImpl() {
        configuration = new ConfigurationImpl(RuntimeType.CLIENT);
    }

    @Override
    public ClientBuilder withConfig(Configuration config) {
        this.configuration = new ConfigurationImpl(config);
        return this;
    }

    public ClientBuilder tlsConfig(TlsConfig tlsConfig) {
        this.tlsConfig = tlsConfig;
        return this;
    }

    @Override
    public ClientBuilder sslContext(SSLContext sslContext) {
        // TODO
        throw new UnsupportedOperationException("Specifying SSLContext is not supported at the moment");
    }

    @Override
    public ClientBuilder keyStore(KeyStore keyStore, char[] password) {
        this.keyStore = keyStore;
        this.keystorePassword = password;
        return this;
    }

    @Override
    public ClientBuilder trustStore(KeyStore trustStore) {
        return trustStore(trustStore, null);
    }

    public ClientBuilder trustStore(KeyStore trustStore, char[] password) {
        this.trustStore = trustStore;
        this.trustStorePassword = password;
        return this;
    }

    @Override
    public ClientBuilder hostnameVerifier(HostnameVerifier verifier) {
        // TODO
        throw new UnsupportedOperationException("Specifying HostnameVerifier is not supported at the moment");
    }

    @Override
    public ClientBuilder executorService(ExecutorService executorService) {
        return this;
    }

    @Override
    public ClientBuilder scheduledExecutorService(ScheduledExecutorService scheduledExecutorService) {
        return this;
    }

    @Override
    public ClientBuilder connectTimeout(long timeout, TimeUnit unit) {
        configuration.property(CONNECT_TIMEOUT, (int) unit.toMillis(timeout));
        return this;
    }

    @Override
    public ClientBuilder readTimeout(long timeout, TimeUnit unit) {
        configuration.property(READ_TIMEOUT, unit.toMillis(timeout));
        return this;
    }

    public ClientBuilder http2(boolean http2) {
        this.http2 = http2;
        return this;
    }

    public ClientBuilder alpn(boolean alpn) {
        this.alpn = alpn;
        return this;
    }

    public ClientBuilder proxy(String proxyHost, int proxyPort) {
        this.proxyPort = proxyPort;
        this.proxyHost = proxyHost;
        return this;
    }

    public ClientBuilder proxyPassword(String proxyPassword) {
        this.proxyPassword = proxyPassword;
        return this;
    }

    public ClientBuilder proxyUser(String proxyUser) {
        this.proxyUser = proxyUser;
        return this;
    }

    public ClientBuilder followRedirects(boolean followRedirects) {
        this.followRedirects = followRedirects;
        return this;
    }

    public ClientBuilder multiQueryParamMode(MultiQueryParamMode multiQueryParamMode) {
        this.multiQueryParamMode = multiQueryParamMode;
        return this;
    }

    public ClientBuilder loggingScope(LoggingScope loggingScope) {
        this.loggingScope = loggingScope;
        return this;
    }

    public ClientBuilder loggingBodySize(Integer loggingBodySize) {
        this.loggingBodySize = loggingBodySize;
        return this;
    }

    public ClientBuilder clientLogger(ClientLogger clientLogger) {
        this.clientLogger = clientLogger;
        return this;
    }

    public ClientBuilder enableCompression() {
        this.enableCompression = true;
        return this;
    }

    public ClientBuilder maxChunkSize(int maxChunkSize) {
        this.maxChunkSize = maxChunkSize;
        return this;
    }

    @Override
    public ClientImpl build() {
        HttpClientOptions options = Optional.ofNullable(configuration.getFromContext(HttpClientOptions.class))
                .orElseGet(HttpClientOptions::new);
        if (http2) {
            options.setProtocolVersion(HttpVersion.HTTP_2);
        }

        if (http2 || alpn) {
            options.setUseAlpn(true);
            options.setAlpnVersions(List.of(HttpVersion.HTTP_2, HttpVersion.HTTP_1_1));
        }

        if (tlsConfig != null) {
            populateSecurityOptionsFromTlsConfig(options);
        } else {
            populateSecurityOptionsFromExplicitTlsProperties(options);
        }

        if (proxyHost != null) {
            if (!"none".equals(proxyHost)) {
                ProxyOptions proxyOptions = new ProxyOptions()
                        .setHost(proxyHost)
                        .setPort(proxyPort);
                if (proxyPassword != null && !proxyPassword.isBlank()) {
                    proxyOptions.setPassword(proxyPassword);
                }
                if (proxyUser != null && !proxyUser.isBlank()) {
                    proxyOptions.setUsername(proxyUser);
                }
                options.setProxyOptions(proxyOptions);
                configureNonProxyHosts(options, nonProxyHosts);
            }
        } else {
            String proxyHost = options.isSsl()
                    ? System.getProperty("https.proxyHost", "none")
                    : System.getProperty("http.proxyHost", "none");
            String proxyPortAsString = options.isSsl()
                    ? System.getProperty("https.proxyPort", "443")
                    : System.getProperty("http.proxyPort", "80");
            String nonProxyHosts = options.isSsl()
                    ? System.getProperty("https.nonProxyHosts", "localhost|127.*|[::1]")
                    : System.getProperty("http.nonProxyHosts", "localhost|127.*|[::1]");
            int proxyPort = Integer.parseInt(proxyPortAsString);

            if (!"none".equals(proxyHost)) {
                ProxyOptions proxyOptions = new ProxyOptions().setHost(proxyHost).setPort(proxyPort);
                proxyUser = options.isSsl()
                        ? System.getProperty("https.proxyUser")
                        : System.getProperty("http.proxyUser");
                if (proxyUser != null && !proxyUser.isBlank()) {
                    proxyOptions.setUsername(proxyUser);
                }
                proxyPassword = options.isSsl()
                        ? System.getProperty("https.proxyPassword")
                        : System.getProperty("http.proxyPassword");
                if (proxyPassword != null && !proxyPassword.isBlank()) {
                    proxyOptions.setPassword(proxyPassword);
                }
                options.setProxyOptions(proxyOptions);
                if (nonProxyHosts != null) {
                    configureNonProxyHosts(options, nonProxyHosts);
                }
            }
        }

        if (enableCompression) {
            configuration.register(ClientGZIPDecodingInterceptor.class);
        }

        clientLogger.setBodySize(loggingBodySize);

        options.setMaxChunkSize(maxChunkSize);
        return new ClientImpl(options,
                configuration,
                CLIENT_CONTEXT_RESOLVER.resolve(Thread.currentThread().getContextClassLoader()),
                null,
                null,
                followRedirects,
                multiQueryParamMode,
                loggingScope,
                clientLogger, userAgent);

    }

    private void populateSecurityOptionsFromTlsConfig(HttpClientOptions options) {
        options.setSsl(true);

        if (tlsConfig.getTrustStoreOptions() != null) {
            options.setTrustOptions(tlsConfig.getTrustStoreOptions());
        }

        // For mTLS:
        if (tlsConfig.getKeyStoreOptions() != null) {
            options.setKeyCertOptions(tlsConfig.getKeyStoreOptions());
        }

        if (tlsConfig.isTrustAll()) {
            options.setTrustAll(true);
        }
        if (tlsConfig.getHostnameVerificationAlgorithm().isPresent()
                && tlsConfig.getHostnameVerificationAlgorithm().get().equals("NONE")) {
            // Only disable hostname verification if the algorithm is explicitly set to NONE
            options.setVerifyHost(false);
        }

        SSLOptions sslOptions = tlsConfig.getSSLOptions();
        if (sslOptions != null) {
            options.setSslHandshakeTimeout(sslOptions.getSslHandshakeTimeout());
            options.setSslHandshakeTimeoutUnit(sslOptions.getSslHandshakeTimeoutUnit());
            for (String suite : sslOptions.getEnabledCipherSuites()) {
                options.addEnabledCipherSuite(suite);
            }
            for (Buffer buffer : sslOptions.getCrlValues()) {
                options.addCrlValue(buffer);
            }
            options.setEnabledSecureTransportProtocols(sslOptions.getEnabledSecureTransportProtocols());
            options.setUseAlpn(sslOptions.isUseAlpn());
        }
    }

    private void populateSecurityOptionsFromExplicitTlsProperties(HttpClientOptions options) {
        options.setVerifyHost(verifyHost);
        if (trustAll) {
            options.setTrustAll(true);
            options.setVerifyHost(false);
        }

        char[] effectiveTrustStorePassword = trustStorePassword == null ? EMPTY_CHAR_ARRAY : trustStorePassword;
        Buffer keyStore = asBuffer(this.keyStore, keystorePassword);
        Buffer trustStore = asBuffer(this.trustStore, effectiveTrustStorePassword);
        if (keyStore != null || trustStore != null) {
            options.setSsl(true);
            if (keyStore != null) {
                JksOptions jks = new JksOptions();
                jks.setValue(keyStore);
                jks.setPassword(new String(keystorePassword));
                options.setKeyStoreOptions(jks);
            }
            if (trustStore != null) {
                JksOptions jks = new JksOptions();
                jks.setValue(trustStore);
                jks.setPassword(new String(effectiveTrustStorePassword));
                options.setTrustStoreOptions(jks);
            }
        }
    }

    private void configureNonProxyHosts(HttpClientOptions options, String nonProxyHosts) {
        if (nonProxyHosts != null) {
            for (String host : nonProxyHosts.split(PIPE)) {
                if (!host.isBlank()) {
                    options.addNonProxyHost(host);
                }
            }
        }
    }

    private Buffer asBuffer(KeyStore keyStore, char[] password) {
        if (keyStore != null) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            try {
                keyStore.store(out, password);
                return Buffer.buffer(out.toByteArray());
            } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) {
                log.error("Failed to translate keystore to vert.x keystore", e);
            }
        }
        return null;
    }

    @Override
    public Configuration getConfiguration() {
        return configuration;
    }

    @Override
    public ClientBuilder property(String name, Object value) {
        configuration.property(name, value);
        return this;
    }

    @Override
    public ClientBuilderImpl register(Class componentClass) {
        configuration.register(componentClass);
        return this;
    }

    @Override
    public ClientBuilderImpl register(Class componentClass, int priority) {
        configuration.register(componentClass, priority);
        return this;
    }

    @Override
    public ClientBuilderImpl register(Class componentClass, Class... contracts) {
        configuration.register(componentClass, contracts);
        return this;
    }

    @Override
    public ClientBuilderImpl register(Class componentClass, Map, Integer> contracts) {
        configuration.register(componentClass, contracts);
        return this;
    }

    @Override
    public ClientBuilderImpl register(Object component) {
        configuration.register(component);
        return this;
    }

    @Override
    public ClientBuilderImpl register(Object component, int priority) {
        configuration.register(component, priority);
        return this;
    }

    @Override
    public ClientBuilderImpl register(Object component, Class... contracts) {
        configuration.register(component, contracts);
        return this;
    }

    @Override
    public ClientBuilderImpl register(Object component, Map, Integer> contracts) {
        configuration.register(component, contracts);
        return this;
    }

    /*
     * Add some custom methods that allow registering MessageBodyReader and MessageBodyWriter classes with all the necessary
     * information
     */

    public ClientBuilderImpl registerMessageBodyReader(MessageBodyReader reader, Class handledType, List consumes,
            boolean builtin, Integer priority) {
        configuration.registerMessageBodyReader(reader, handledType, consumes, RuntimeType.CLIENT, builtin, priority);
        return this;
    }

    public ClientBuilderImpl registerMessageBodyWriter(MessageBodyWriter writer, Class handledType, List consumes,
            boolean builtin, Integer priority) {
        configuration.registerMessageBodyWriter(writer, handledType, consumes, RuntimeType.CLIENT, builtin, priority);
        return this;
    }

    public ClientBuilderImpl trustAll(boolean trustAll) {
        this.trustAll = trustAll;
        return this;
    }

    public ClientBuilderImpl verifyHost(boolean verifyHost) {
        this.verifyHost = verifyHost;
        return this;
    }

    public ClientBuilderImpl setUserAgent(String userAgent) {
        this.userAgent = userAgent;
        return this;
    }

    public ClientBuilderImpl nonProxyHosts(String nonProxyHosts) {
        this.nonProxyHosts = nonProxyHosts;
        return this;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy