aboutsummaryrefslogtreecommitdiffstats
path: root/http-utils
diff options
context:
space:
mode:
authorJon Marius Venstad <venstad@gmail.com>2021-03-23 12:53:38 +0100
committerJon Marius Venstad <venstad@gmail.com>2021-03-23 12:53:38 +0100
commit6169d5ddcc3c69750830b326c07c05fac4de0c1f (patch)
tree9e1aab09b177f1abc07253b2928ca5c4d4d2d1db /http-utils
parent74b336748b52103d2a4e6b6f5cc9170348c0ab51 (diff)
Add builder for synchronous hc5 clients
Diffstat (limited to 'http-utils')
-rw-r--r--http-utils/src/main/java/ai/vespa/util/http/hc5/HttpToHttpsRoutePlanner.java35
-rw-r--r--http-utils/src/main/java/ai/vespa/util/http/hc5/VespaAsyncHttpClientBuilder.java30
-rw-r--r--http-utils/src/main/java/ai/vespa/util/http/hc5/VespaHttpClientBuilder.java80
3 files changed, 115 insertions, 30 deletions
diff --git a/http-utils/src/main/java/ai/vespa/util/http/hc5/HttpToHttpsRoutePlanner.java b/http-utils/src/main/java/ai/vespa/util/http/hc5/HttpToHttpsRoutePlanner.java
new file mode 100644
index 00000000000..672a2fd3918
--- /dev/null
+++ b/http-utils/src/main/java/ai/vespa/util/http/hc5/HttpToHttpsRoutePlanner.java
@@ -0,0 +1,35 @@
+// Copyright Verizon Media. 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.HttpRoute;
+import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
+import org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.routing.HttpRoutePlanner;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.protocol.HttpContext;
+
+/**
+ * {@link HttpRoutePlanner} that changes assumes requests specify the HTTP scheme,
+ * and then changes this to HTTPS, keeping the other host parameters.
+ *
+ * @author jonmv
+ */
+class HttpToHttpsRoutePlanner implements HttpRoutePlanner {
+
+ @Override
+ public HttpRoute determineRoute(HttpHost target, HttpContext context) throws HttpException {
+ if ( ! target.getSchemeName().equals("http"))
+ throw new IllegalArgumentException("Scheme must be 'http' when using HttpToHttpsRoutePlanner");
+
+ if (target.getPort() == -1)
+ throw new IllegalArgumentException("Port must be set when using HttpToHttpsRoutePlanner");
+
+ if (HttpClientContext.adapt(context).getRequestConfig().getProxy() != null)
+ throw new IllegalArgumentException("Proxies are not supported with HttpToHttpsRoutePlanner");
+
+ return new HttpRoute(new HttpHost("https", target.getAddress(), target.getHostName(), target.getPort()));
+ }
+
+}
diff --git a/http-utils/src/main/java/ai/vespa/util/http/hc5/VespaAsyncHttpClientBuilder.java b/http-utils/src/main/java/ai/vespa/util/http/hc5/VespaAsyncHttpClientBuilder.java
index ed28f328839..219f1707589 100644
--- a/http-utils/src/main/java/ai/vespa/util/http/hc5/VespaAsyncHttpClientBuilder.java
+++ b/http-utils/src/main/java/ai/vespa/util/http/hc5/VespaAsyncHttpClientBuilder.java
@@ -4,19 +4,12 @@ package ai.vespa.util.http.hc5;
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;
@@ -70,27 +63,4 @@ public class VespaAsyncHttpClientBuilder {
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());
- }
- }
-
}
diff --git a/http-utils/src/main/java/ai/vespa/util/http/hc5/VespaHttpClientBuilder.java b/http-utils/src/main/java/ai/vespa/util/http/hc5/VespaHttpClientBuilder.java
new file mode 100644
index 00000000000..2824a1b801d
--- /dev/null
+++ b/http-utils/src/main/java/ai/vespa/util/http/hc5/VespaHttpClientBuilder.java
@@ -0,0 +1,80 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package ai.vespa.util.http.hc5;
+
+import com.yahoo.security.tls.TransportSecurityUtils;
+import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
+import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
+import org.apache.hc.client5.http.io.HttpClientConnectionManager;
+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 javax.net.ssl.SSLParameters;
+
+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 <em>for internal Vespa communications over http/https.</em>
+ *
+ * Configures Vespa mTLS and handles TLS mixed mode automatically.
+ * Custom connection managers must be configured through {@link #create(HttpClientConnectionManagerFactory)}.
+ *
+ * @author jonmv
+ */
+public class VespaHttpClientBuilder {
+
+ public interface HttpClientConnectionManagerFactory {
+ HttpClientConnectionManager create(Registry<ConnectionSocketFactory> socketFactories);
+ }
+
+ public static HttpClientBuilder create() {
+ return create(PoolingHttpClientConnectionManager::new);
+ }
+
+ public static HttpClientBuilder create(HttpClientConnectionManagerFactory connectionManagerFactory) {
+ HttpClientBuilder builder = HttpClientBuilder.create();
+ addSslSocketFactory(builder, connectionManagerFactory);
+ addHttpsRewritingRoutePlanner(builder);
+
+ builder.disableConnectionState(); // Share connections between subsequent requests.
+ builder.disableCookieManagement();
+ builder.disableAuthCaching();
+ builder.disableRedirectHandling();
+
+ return builder;
+ }
+
+ private static void addSslSocketFactory(HttpClientBuilder builder, HttpClientConnectionManagerFactory connectionManagerFactory) {
+ getSystemTlsContext().ifPresent(tlsContext -> {
+ SSLParameters parameters = tlsContext.parameters();
+ SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(tlsContext.context(),
+ parameters.getProtocols(),
+ parameters.getCipherSuites(),
+ new NoopHostnameVerifier());
+ 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<ConnectionSocketFactory> createRegistry(SSLConnectionSocketFactory sslSocketFactory) {
+ return RegistryBuilder.<ConnectionSocketFactory>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());
+ }
+
+}