diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseClientOption.java b/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseClientOption.java
index 95187b541..1f410f2ab 100644
--- a/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseClientOption.java
+++ b/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseClientOption.java
@@ -451,7 +451,14 @@ public enum ClickHouseClientOption implements ClickHouseOption {
*/
CONNECTION_TTL("connection_ttl", 0L,
"Connection time to live in milliseconds. 0 or negative number means no limit."),
- MEASURE_REQUEST_TIME("debug_measure_request_time", false, "Whether to measure request time. If true, the time will be logged in debug mode.");
+ MEASURE_REQUEST_TIME("debug_measure_request_time", false, "Whether to measure request time. If true, the time will be logged in debug mode."),
+
+ /**
+ * SNI SSL parameter that will be set for each outbound SSL socket.
+ */
+ SSL_SOCKET_SNI("ssl_socket_sni", "", " SNI SSL parameter that will be set for each outbound SSL socket.")
+
+ ;
private final String key;
private final Serializable defaultValue;
diff --git a/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ApacheHttpConnectionImpl.java b/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ApacheHttpConnectionImpl.java
index e5689b4f9..0c00f7015 100644
--- a/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ApacheHttpConnectionImpl.java
+++ b/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ApacheHttpConnectionImpl.java
@@ -11,6 +11,7 @@
import com.clickhouse.client.config.ClickHouseProxyType;
import com.clickhouse.client.config.ClickHouseSslMode;
import com.clickhouse.client.http.config.ClickHouseHttpOption;
+import com.clickhouse.config.ClickHouseOption;
import com.clickhouse.data.ClickHouseChecker;
import com.clickhouse.data.ClickHouseExternalTable;
import com.clickhouse.data.ClickHouseFormat;
@@ -54,8 +55,11 @@
import org.apache.hc.core5.util.Timeout;
import org.apache.hc.core5.util.VersionInfo;
+import javax.net.ssl.SNIHostName;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSocket;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -66,10 +70,9 @@
import java.io.UncheckedIOException;
import java.net.ConnectException;
import java.net.HttpURLConnection;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
-import java.net.SocketOption;
-import java.net.SocketOptions;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
@@ -394,6 +397,7 @@ public static SocketFactory create(ClickHouseConfig config) {
static class SSLSocketFactory extends SSLConnectionSocketFactory {
private final ClickHouseConfig config;
+ private final SNIHostName defaultSNI;
private SSLSocketFactory(ClickHouseConfig config) throws SSLException {
super(ClickHouseSslContextProvider.getProvider().getSslContext(SSLContext.class, config)
@@ -402,6 +406,8 @@ private SSLSocketFactory(ClickHouseConfig config) throws SSLException {
? new DefaultHostnameVerifier()
: (hostname, session) -> true); // NOSONAR
this.config = config;
+ String sni = config.getStrOption(ClickHouseClientOption.SSL_SOCKET_SNI);
+ defaultSNI = sni == null || sni.trim().isEmpty() ? null : new SNIHostName(sni);
}
@Override
@@ -409,6 +415,16 @@ public Socket createSocket(HttpContext context) throws IOException {
return AbstractSocketClient.setSocketOptions(config, new Socket());
}
+ @Override
+ protected void prepareSocket(SSLSocket socket, HttpContext context) throws IOException {
+ super.prepareSocket(socket, context);
+ if (defaultSNI != null) {
+ SSLParameters sslParams = socket.getSSLParameters();
+ sslParams.setServerNames(Collections.singletonList(defaultSNI));
+ socket.setSSLParameters(sslParams);
+ }
+ }
+
public static SSLSocketFactory create(ClickHouseConfig config) throws SSLException {
return new SSLSocketFactory(config);
}
diff --git a/client-v2/pom.xml b/client-v2/pom.xml
index a610c0ef5..7e64b8a8a 100644
--- a/client-v2/pom.xml
+++ b/client-v2/pom.xml
@@ -129,6 +129,12 @@
1.18.36
test
+
+ org.slf4j
+ slf4j-simple
+ 2.0.16
+ test
+
diff --git a/client-v2/src/main/java/com/clickhouse/client/api/Client.java b/client-v2/src/main/java/com/clickhouse/client/api/Client.java
index 7b176b64e..344643d44 100644
--- a/client-v2/src/main/java/com/clickhouse/client/api/Client.java
+++ b/client-v2/src/main/java/com/clickhouse/client/api/Client.java
@@ -602,6 +602,11 @@ public Builder useHttpCompression(boolean enabled) {
return this;
}
+ /**
+ * Tell client that compression will be handled by application.
+ * @param enabled - indicates that feature is enabled.
+ * @return
+ */
public Builder appCompressedData(boolean enabled) {
this.configuration.put(ClientConfigProperties.APP_COMPRESSED_DATA.getKey(), String.valueOf(enabled));
return this;
@@ -1025,6 +1030,19 @@ public Builder typeHintMapping(Map> typeHintMapping
return this;
}
+
+ /**
+ * SNI SSL parameter that will be set for each outbound SSL socket.
+ * SNI stands for Server Name Indication - an extension to the TLS protocol that allows multiple domains to share the same IP address.
+ *
+ * @param sni - SNI parameter
+ * @return this builder instance
+ */
+ public Builder sslSocketSNI(String sni) {
+ this.configuration.put(ClientConfigProperties.SSL_SOCKET_SNI.getKey(), sni);
+ return this;
+ }
+
public Client build() {
// check if endpoint are empty. so can not initiate client
if (this.endpoints.isEmpty()) {
diff --git a/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java b/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java
index cf8caee8f..89e0f5a12 100644
--- a/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java
+++ b/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java
@@ -11,16 +11,15 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.LinkedHashMap;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.function.Function;
-import java.util.Map;
import java.util.TimeZone;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.stream.Collectors;
/**
@@ -178,6 +177,11 @@ public Object parseValue(String value) {
* Used by binary readers to convert values into desired Java type.
*/
TYPE_HINT_MAPPING("type_hint_mapping", Map.class),
+
+ /**
+ * SNI SSL parameter that will be set for each outbound SSL socket.
+ */
+ SSL_SOCKET_SNI("ssl_socket_sni", String.class,""),
;
private static final Logger LOG = LoggerFactory.getLogger(ClientConfigProperties.class);
@@ -218,6 +222,9 @@ public T getDefObjVal() {
public static final String SERVER_SETTING_PREFIX = "clickhouse_setting_";
+ // Key used to identify default value in configuration map
+ public static final String DEFAULT_KEY = "_default_";
+
public static String serverSetting(String key) {
return SERVER_SETTING_PREFIX + key;
}
diff --git a/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java b/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java
index 7f6f442f5..a9aadd66a 100644
--- a/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java
+++ b/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java
@@ -60,8 +60,12 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SNIHostName;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -256,8 +260,17 @@ public CloseableHttpClient createHttpClient(boolean initSslContext, Map true);
+ } else {
+ sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext);
+ }
+ } else {
+ sslConnectionSocketFactory = new DummySSLConnectionSocketFactory();
+ }
// Socket configuration
SocketConfig.Builder soCfgBuilder = SocketConfig.custom();
ClientConfigProperties.SOCKET_OPERATION_TIMEOUT.applyIfSet(configuration,
@@ -834,4 +847,25 @@ public long getTime() {
return count > 0 ? runningAverage / count : 0;
}
}
+
+ public static class CustomSSLConnectionFactory extends SSLConnectionSocketFactory {
+
+ private final SNIHostName defaultSNI;
+
+ public CustomSSLConnectionFactory(String defaultSNI, SSLContext sslContext, HostnameVerifier hostnameVerifier) {
+ super(sslContext, hostnameVerifier);
+ this.defaultSNI = defaultSNI == null || defaultSNI.trim().isEmpty() ? null : new SNIHostName(defaultSNI);
+ }
+
+ @Override
+ protected void prepareSocket(SSLSocket socket, HttpContext context) throws IOException {
+ super.prepareSocket(socket, context);
+
+ if (defaultSNI != null) {
+ SSLParameters sslParams = socket.getSSLParameters();
+ sslParams.setServerNames(Collections.singletonList(defaultSNI));
+ socket.setSSLParameters(sslParams);
+ }
+ }
+ }
}
diff --git a/client-v2/src/test/java/com/clickhouse/client/ClientTests.java b/client-v2/src/test/java/com/clickhouse/client/ClientTests.java
index 2d56941e9..da6b38658 100644
--- a/client-v2/src/test/java/com/clickhouse/client/ClientTests.java
+++ b/client-v2/src/test/java/com/clickhouse/client/ClientTests.java
@@ -206,7 +206,7 @@ public void testDefaultSettings() {
Assert.assertEquals(config.get(p.getKey()), p.getDefaultValue(), "Default value doesn't match");
}
}
- Assert.assertEquals(config.size(), 31); // to check everything is set. Increment when new added.
+ Assert.assertEquals(config.size(), 32); // to check everything is set. Increment when new added.
}
try (Client client = new Client.Builder()
@@ -239,7 +239,7 @@ public void testDefaultSettings() {
.setSocketSndbuf(100000)
.build()) {
Map config = client.getConfiguration();
- Assert.assertEquals(config.size(), 32); // to check everything is set. Increment when new added.
+ Assert.assertEquals(config.size(), 33); // to check everything is set. Increment when new added.
Assert.assertEquals(config.get(ClientConfigProperties.DATABASE.getKey()), "mydb");
Assert.assertEquals(config.get(ClientConfigProperties.MAX_EXECUTION_TIME.getKey()), "10");
Assert.assertEquals(config.get(ClientConfigProperties.COMPRESSION_LZ4_UNCOMPRESSED_BUF_SIZE.getKey()), "300000");
@@ -306,7 +306,7 @@ public void testWithOldDefaults() {
Assert.assertEquals(config.get(p.getKey()), p.getDefaultValue(), "Default value doesn't match");
}
}
- Assert.assertEquals(config.size(), 31); // to check everything is set. Increment when new added.
+ Assert.assertEquals(config.size(), 32); // to check everything is set. Increment when new added.
}
}
diff --git a/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java b/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java
index 9ff751068..ae495c786 100644
--- a/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java
+++ b/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java
@@ -36,6 +36,7 @@
import org.testng.annotations.Test;
import java.io.ByteArrayInputStream;
+import java.net.InetAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
@@ -1100,6 +1101,23 @@ public void testTimeoutsWithRetry() {
}
}
+ @Test(groups = {"integration"})
+ public void testSNIWithCloud() throws Exception {
+ if (!isCloud()) {
+ // skip for local env
+ return;
+ }
+
+ ClickHouseNode node = getServer(ClickHouseProtocol.HTTP);
+ String ip = InetAddress.getByName(node.getHost()).getHostAddress();
+ try (Client c = new Client.Builder()
+ .addEndpoint(Protocol.HTTP, ip, node.getPort(), true)
+ .setUsername("default")
+ .setPassword(ClickHouseServerForTest.getPassword())
+ .sslSocketSNI(node.getHost()).build()) {
+ c.execute("SELECT 1");
+ }
+ }
protected Client.Builder newClient() {
ClickHouseNode node = getServer(ClickHouseProtocol.HTTP);
diff --git a/client-v2/src/test/java/com/clickhouse/client/internal/SmallTests.java b/client-v2/src/test/java/com/clickhouse/client/internal/SmallTests.java
index 5a1755777..ff42c0ced 100644
--- a/client-v2/src/test/java/com/clickhouse/client/internal/SmallTests.java
+++ b/client-v2/src/test/java/com/clickhouse/client/internal/SmallTests.java
@@ -1,5 +1,6 @@
package com.clickhouse.client.internal;
+import com.clickhouse.client.api.ClientConfigProperties;
import com.clickhouse.client.api.data_formats.internal.ProcessParser;
import com.clickhouse.client.api.metrics.OperationMetrics;
import com.clickhouse.client.api.metrics.ServerMetrics;
@@ -45,4 +46,19 @@ public void testTimezoneConvertion() {
ZonedDateTime utcSameLocalDt = dt.withZoneSameLocal(ZoneId.of("UTC"));
System.out.println("withZoneSameLocal: " + utcSameLocalDt);
}
+
+ @Test
+ public void testGenConfigParameters() {
+ System.out.println("
Default: `none`
Enum: `none`
Key: `none` "
+
+
+ );
+ for (ClientConfigProperties p : ClientConfigProperties.values()) {
+ String defaultValue = p.getDefaultValue() == null ? "-" : "`" + p.getDefaultValue() + "`";
+ System.out.println("
Default: " +defaultValue + "
Enum: `ClientConfigProperties." + p.name() + "`
Key: `" + p.getKey() +"` "
+
+
+ );
+ }
+ }
}