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

ai.vespa.util.http.hc5.VespaHttpClientBuilder Maven / Gradle / Ivy

There is a newer version: 8.441.21
Show newest version
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package ai.vespa.util.http.hc5;

import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;

import javax.net.ssl.HostnameVerifier;

import java.util.concurrent.TimeUnit;

import static com.yahoo.security.tls.MixedMode.PLAINTEXT_CLIENT_MIXED_SERVER;
import static com.yahoo.security.tls.TransportSecurityUtils.getInsecureMixedMode;
import static com.yahoo.security.tls.TransportSecurityUtils.getSystemTlsContext;
import static com.yahoo.security.tls.TransportSecurityUtils.isTransportSecurityEnabled;

/**
 * Sync HTTP client builder for internal Vespa communications over http/https.
 * Configures Vespa mTLS and handles TLS mixed mode automatically.
 * Custom connection managers must be configured through {@link #connectionManagerFactory(HttpClientConnectionManagerFactory)}.
 *
 * @author jonmv
 */
public class VespaHttpClientBuilder {

    private HttpClientConnectionManagerFactory connectionManagerFactory = PoolingHttpClientConnectionManager::new;
    private HostnameVerifier hostnameVerifier = new NoopHostnameVerifier();
    private boolean rewriteHttpToHttps = true;
    private final ConnectionConfig.Builder connectionConfigBuilder = ConnectionConfig.custom();

    public interface HttpClientConnectionManagerFactory {
        PoolingHttpClientConnectionManager create(Registry socketFactories);
    }

    private VespaHttpClientBuilder() {
    }

    public static VespaHttpClientBuilder custom() {
        return new VespaHttpClientBuilder();
    }

    public VespaHttpClientBuilder connectionManagerFactory(HttpClientConnectionManagerFactory connectionManagerFactory) {
        this.connectionManagerFactory = connectionManagerFactory;
        return this;
    }

    public VespaHttpClientBuilder hostnameVerifier(HostnameVerifier hostnameVerifier) {
        this.hostnameVerifier = hostnameVerifier;
        return this;
    }
    public VespaHttpClientBuilder rewriteHttpToHttps(boolean enable) {
        this.rewriteHttpToHttps = enable;
        return this;
    }
    public VespaHttpClientBuilder connectTimeout(long connectTimeout, TimeUnit timeUnit) {
        connectionConfigBuilder.setConnectTimeout(connectTimeout, timeUnit);
        return this;
    }
    public VespaHttpClientBuilder connectTimeout(Timeout connectTimeout) {
        connectionConfigBuilder.setConnectTimeout(connectTimeout);
        return this;
    }
    public VespaHttpClientBuilder socketTimeout(int socketTimeout, TimeUnit timeUnit) {
        connectionConfigBuilder.setSocketTimeout(socketTimeout, timeUnit);
        return this;
    }
    public VespaHttpClientBuilder socketTimeout(Timeout socketTimeout) {
        connectionConfigBuilder.setSocketTimeout(socketTimeout);
        return this;
    }
    public VespaHttpClientBuilder validateAfterInactivity(TimeValue validateAfterInactivity) {
        connectionConfigBuilder.setValidateAfterInactivity(validateAfterInactivity);
        return this;
    }

    public HttpClientBuilder apacheBuilder() {
        HttpClientBuilder builder = HttpClientBuilder.create();
        addSslSocketFactory(builder, new HttpClientConnectionManagerFactoryProxy(), hostnameVerifier);
        if (rewriteHttpToHttps)
            addHttpsRewritingRoutePlanner(builder);

        builder.disableConnectionState(); // Share connections between subsequent requests.
        builder.disableCookieManagement();
        builder.disableAuthCaching();
        builder.disableRedirectHandling();

        return builder;
    }
    public CloseableHttpClient buildClient() {
        return apacheBuilder().build();
    }

    private class HttpClientConnectionManagerFactoryProxy implements HttpClientConnectionManagerFactory {
        @Override
        public PoolingHttpClientConnectionManager create(Registry socketFactories) {
            PoolingHttpClientConnectionManager manager = connectionManagerFactory.create(socketFactories);
            manager.setDefaultConnectionConfig(connectionConfigBuilder.build());
            return manager;
        }
    }

    private static void addSslSocketFactory(HttpClientBuilder builder, HttpClientConnectionManagerFactory connectionManagerFactory,
                                            HostnameVerifier hostnameVerifier) {
        getSystemTlsContext().ifPresent(tlsContext -> {
            SSLConnectionSocketFactory socketFactory = SslConnectionSocketFactory.of(tlsContext, hostnameVerifier);
            builder.setConnectionManager(connectionManagerFactory.create(createRegistry(socketFactory)));
            // Workaround that allows re-using https connections, see https://stackoverflow.com/a/42112034/1615280 for details.
            // Proper solution would be to add a request interceptor that adds a x500 principal as user token,
            // but certificate subject CN is not accessible through the TlsContext currently.
            builder.setUserTokenHandler((route, context) -> null);
        });
    }

    private static Registry createRegistry(SSLConnectionSocketFactory sslSocketFactory) {
        return RegistryBuilder.create()
                .register("https", sslSocketFactory)
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .build();
    }

    private static void addHttpsRewritingRoutePlanner(HttpClientBuilder builder) {
        if (isTransportSecurityEnabled() && getInsecureMixedMode() != PLAINTEXT_CLIENT_MIXED_SERVER)
            builder.setRoutePlanner(new HttpToHttpsRoutePlanner());
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy