closeables;
public static HttpClientBuilder create() {
@@ -807,6 +809,20 @@ public final HttpClientBuilder setProxySelector(final ProxySelector proxySelecto
return this;
}
+ /**
+ * When enabled, the client refuses to establish cleartext connections.
+ * This disables plain {@code http://}, {@code h2c}, and RFC 2817 TLS upgrade paths.
+ *
+ * @param tlsRequired whether to enforce TLS-required routes.
+ * @return this instance.
+ *
+ * @since 5.7
+ */
+ public final HttpClientBuilder setTlsRequired(final boolean tlsRequired) {
+ this.tlsRequired = tlsRequired;
+ return this;
+ }
+
/**
* Request exec chain customization and extension.
*
@@ -999,6 +1015,10 @@ public CloseableHttpClient build() {
}
}
+ if (this.tlsRequired) {
+ execChainDefinition.addFirst(new TlsRequiredExec(), ChainElement.TLS_REQUIRED.name());
+ }
+
// Add request retry executor, if not disabled
if (!automaticRetriesDisabled) {
HttpRequestRetryStrategy retryStrategyCopy = this.retryStrategy;
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/TlsRequiredExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/TlsRequiredExec.java
new file mode 100644
index 0000000000..0c155b210c
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/TlsRequiredExec.java
@@ -0,0 +1,59 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+package org.apache.hc.client5.http.impl.classic;
+
+import java.io.IOException;
+
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.UnsupportedSchemeException;
+import org.apache.hc.client5.http.classic.ExecChain;
+import org.apache.hc.client5.http.classic.ExecChainHandler;
+import org.apache.hc.core5.annotation.Internal;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpException;
+
+/**
+ * Internal exec interceptor that enforces the "TLS required" client policy.
+ *
+ */
+@Internal
+final class TlsRequiredExec implements ExecChainHandler {
+
+ @Override
+ public ClassicHttpResponse execute(
+ final ClassicHttpRequest request,
+ final ExecChain.Scope scope,
+ final ExecChain chain) throws IOException, HttpException {
+
+ final HttpRoute route = scope.route;
+ if (route != null && !route.isSecure()) {
+ throw new UnsupportedSchemeException("Cleartext HTTP is disabled (TLS required)");
+ }
+ return chain.proceed(request, scope);
+ }
+}
\ No newline at end of file
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/TlsRequiredAsyncExample.java b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/TlsRequiredAsyncExample.java
new file mode 100644
index 0000000000..c4253977f7
--- /dev/null
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/TlsRequiredAsyncExample.java
@@ -0,0 +1,97 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+package org.apache.hc.client5.http.examples;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+import org.apache.hc.client5.http.UnsupportedSchemeException;
+import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+
+/**
+ * Demonstrates the "TLS-required connections" mode for the async client.
+ *
+ *
+ * When {@code TlsRequired(true)} is enabled, the async client rejects execution when the
+ * computed {@code HttpRoute} is not secure. This prevents accidental cleartext connections
+ * such as {@code http://...} and disables cleartext upgrade mechanisms that start without TLS.
+ *
+ *
+ *
+ * The example triggers a rejection using {@code http://example.com/} and validates the failure
+ * by unwrapping {@link ExecutionException#getCause()} and checking for
+ * {@link UnsupportedSchemeException}.
+ *
+ *
+ * @since 5.7
+ */
+public final class TlsRequiredAsyncExample {
+
+ public static void main(final String[] args) throws Exception {
+ try (final CloseableHttpAsyncClient client = HttpAsyncClients.custom()
+ .setTlsRequired(true)
+ .build()) {
+
+ client.start();
+
+ // 1) Must fail fast with UnsupportedSchemeException
+ final SimpleHttpRequest http = SimpleRequestBuilder.get("http://example.com/").build();
+ final Future httpFuture =
+ client.execute(http, HttpClientContext.create(), null);
+
+ try {
+ final SimpleHttpResponse response = httpFuture.get();
+ System.out.println("UNEXPECTED: http:// executed with status " + response.getCode());
+ } catch (final ExecutionException ex) {
+ final Throwable cause = ex.getCause();
+ if (cause instanceof UnsupportedSchemeException) {
+ System.out.println("OK (expected): " + cause.getMessage());
+ } else {
+ throw ex;
+ }
+ }
+
+ // 2) Allowed (may still fail if network/DNS blocked)
+ final SimpleHttpRequest https = SimpleRequestBuilder.get("https://example.com/").build();
+ final Future httpsFuture =
+ client.execute(https, HttpClientContext.create(), null);
+
+ try {
+ final SimpleHttpResponse response = httpsFuture.get();
+ System.out.println("HTTPS OK: status=" + response.getCode());
+ } catch (final ExecutionException ex) {
+ System.err.println("HTTPS failed (network/env): " + ex.getCause());
+ }
+ }
+ }
+
+}
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/TlsRequiredClassicExample.java b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/TlsRequiredClassicExample.java
new file mode 100644
index 0000000000..edc9b64096
--- /dev/null
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/TlsRequiredClassicExample.java
@@ -0,0 +1,92 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+package org.apache.hc.client5.http.examples;
+
+import java.io.IOException;
+
+import org.apache.hc.client5.http.UnsupportedSchemeException;
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+
+
+/**
+ * Demonstrates the "TLS-required connections" mode.
+ *
+ *
+ * When {@code TlsRequired(true)} is enabled, the client refuses to execute requests whose
+ * computed {@code HttpRoute} is not marked secure. In practice this prevents accidental
+ * cleartext HTTP usage (for example {@code http://...}), and also disables cleartext upgrade
+ * mechanisms such as RFC 2817 "Upgrade to TLS" (which necessarily starts in cleartext).
+ *
+ *
+ *
+ * Notes:
+ *
+ *
+ * - This is an opt-in client policy. Default behavior is unchanged.
+ * - This does not add any extra security guarantees beyond normal TLS behavior; it simply
+ * fails fast when a cleartext route is about to be used.
+ * - If a server speaks plaintext on an {@code https://} endpoint, the TLS handshake will fail
+ * as usual; TLS-required mode does not change that.
+ *
+ *
+ * @since 5.7
+ */
+public final class TlsRequiredClassicExample {
+
+ public static void main(final String[] args) throws Exception {
+ try (final CloseableHttpClient client = HttpClients.custom()
+ .setTlsRequired(true)
+ .build()) {
+
+ // 1) This must fail fast (no connect attempt)
+ final HttpGet http = new HttpGet("http://example.com/");
+ try {
+ client.execute(http, response -> {
+ EntityUtils.consume(response.getEntity());
+ System.out.println("UNEXPECTED: http:// executed with status " + response.getCode());
+ return null;
+ });
+ } catch (final UnsupportedSchemeException ex) {
+ System.out.println("OK (expected): " + ex.getMessage());
+ }
+
+ // 2) This should be allowed (may still fail if network/DNS blocked)
+ final HttpGet https = new HttpGet("https://example.com/");
+ client.execute(https, response -> {
+ EntityUtils.consume(response.getEntity());
+ System.out.println("HTTPS OK: status=" + response.getCode());
+ return null;
+ });
+ } catch (final IOException ex) {
+ System.err.println("I/O error: " + ex.getClass().getName() + ": " + ex.getMessage());
+ }
+ }
+
+}