aboutsummaryrefslogtreecommitdiffstats
path: root/http-utils
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@verizonmedia.com>2020-11-24 17:06:00 +0100
committerBjørn Christian Seime <bjorncs@verizonmedia.com>2020-11-24 17:21:15 +0100
commit3af0e8690fdf39a93894f5209dd8124011440287 (patch)
tree10a2e5399d0107535a767705737f664ba4cb4562 /http-utils
parentf78358e385a3f6bb28864fa2f141501d03fb9de7 (diff)
Implement Vespa http client builder for Apache http async client 5
Diffstat (limited to 'http-utils')
-rw-r--r--http-utils/pom.xml5
-rw-r--r--http-utils/src/main/java/ai/vespa/util/http/VespaAsyncHttpClientBuilder.java93
2 files changed, 98 insertions, 0 deletions
diff --git a/http-utils/pom.xml b/http-utils/pom.xml
index 6d2e009cf8c..aa261574285 100644
--- a/http-utils/pom.xml
+++ b/http-utils/pom.xml
@@ -38,6 +38,11 @@
<artifactId>httpcore</artifactId>
<scope>compile</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents.client5</groupId>
+ <artifactId>httpclient5</artifactId>
+ <scope>compile</scope>
+ </dependency>
<!-- test scope -->
<dependency>
diff --git a/http-utils/src/main/java/ai/vespa/util/http/VespaAsyncHttpClientBuilder.java b/http-utils/src/main/java/ai/vespa/util/http/VespaAsyncHttpClientBuilder.java
new file mode 100644
index 00000000000..00f16e5d7c7
--- /dev/null
+++ b/http-utils/src/main/java/ai/vespa/util/http/VespaAsyncHttpClientBuilder.java
@@ -0,0 +1,93 @@
+// 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.setConnectionManager(factory.create(tlsStrategy));
+ 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());
+ }
+ }
+
+}