aboutsummaryrefslogtreecommitdiffstats
path: root/http-utils/src/main/java/ai/vespa/util/http/VespaAsyncHttpClientBuilder.java
blob: 6c53ea0dc69845b08e523d1b257e12b14816d715 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package ai.vespa.util.http;

import com.yahoo.security.tls.MixedMode;
import com.yahoo.security.tls.TlsContext;
import com.yahoo.security.tls.TransportSecurityUtils;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
import org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner;
import org.apache.hc.client5.http.nio.AsyncClientConnectionManager;
import org.apache.hc.client5.http.routing.HttpRoutePlanner;
import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
import org.apache.hc.core5.http.protocol.HttpContext;

import javax.net.ssl.SSLParameters;

/**
 * Async http client builder for internal Vespa communications over http/https.
 * Configures Vespa mTLS and handles TLS mixed mode automatically.
 * Client should only be used for requests to Vespa services.
 *
 * Caveats:
 * - custom connection manager must be configured through {@link #create(AsyncConnectionManagerFactory)}.
 *
 * @author bjorncs
 */
public class VespaAsyncHttpClientBuilder {

    public interface AsyncConnectionManagerFactory {
        AsyncClientConnectionManager create(TlsStrategy tlsStrategy);
    }

    public static HttpAsyncClientBuilder create() {
        return create(
                tlsStrategy -> PoolingAsyncClientConnectionManagerBuilder.create()
                        .setTlsStrategy(tlsStrategy)
                        .build());
    }

    public static HttpAsyncClientBuilder create(AsyncConnectionManagerFactory factory) {
        HttpAsyncClientBuilder clientBuilder = HttpAsyncClientBuilder.create();
        TlsContext vespaTlsContext = TransportSecurityUtils.createTlsContext().orElse(null);
        TlsStrategy tlsStrategy;
        if (vespaTlsContext != null) {
            SSLParameters vespaTlsParameters = vespaTlsContext.parameters();
            tlsStrategy = ClientTlsStrategyBuilder.create()
                    .setHostnameVerifier(new NoopHostnameVerifier())
                    .setSslContext(vespaTlsContext.context())
                    .setTlsVersions(vespaTlsParameters.getProtocols())
                    .setCiphers(vespaTlsParameters.getCipherSuites())
                    .build();
            if (TransportSecurityUtils.getInsecureMixedMode() != MixedMode.PLAINTEXT_CLIENT_MIXED_SERVER) {
                clientBuilder.setRoutePlanner(new HttpToHttpsRoutePlanner());
            }
        } else {
            tlsStrategy = ClientTlsStrategyBuilder.create().build();
        }
        clientBuilder.disableConnectionState(); // Share connections between subsequent requests
        clientBuilder.disableCookieManagement();
        clientBuilder.disableAuthCaching();
        clientBuilder.disableRedirectHandling();
        clientBuilder.setConnectionManager(factory.create(tlsStrategy));
        clientBuilder.setConnectionManagerShared(false);
        return clientBuilder;
    }

    private static class HttpToHttpsRoutePlanner implements HttpRoutePlanner {

        private final DefaultRoutePlanner defaultPlanner = new DefaultRoutePlanner(new DefaultSchemePortResolver());

        @Override
        public HttpRoute determineRoute(HttpHost target, HttpContext context) throws HttpException {
            HttpRoute originalRoute = defaultPlanner.determineRoute(target, context);
            HttpHost originalHost = originalRoute.getTargetHost();
            String originalScheme = originalHost.getSchemeName();
            String rewrittenScheme = originalScheme.equalsIgnoreCase("http") ? "https" : originalScheme;
            boolean rewrittenSecure = target.getSchemeName().equalsIgnoreCase("https");
            HttpHost rewrittenHost = new HttpHost(
                    rewrittenScheme, originalHost.getAddress(), originalHost.getHostName(), originalHost.getPort());
            return new HttpRoute(
                    rewrittenHost,
                    originalRoute.getLocalAddress(),
                    originalRoute.getProxyHost(),
                    rewrittenSecure,
                    originalRoute.getTunnelType(),
                    originalRoute.getLayerType());
        }
    }

}