Skip to content

Commit 2cb4140

Browse files
committed
Merge branch 'main' into feat_support_https
2 parents 74863ab + 97bd8a1 commit 2cb4140

File tree

5 files changed

+271
-55
lines changed

5 files changed

+271
-55
lines changed

client-v2/src/main/java/com/clickhouse/client/api/Client.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,12 @@ public Builder addProxy(ProxyType type, String host, int port) {
424424
return this;
425425
}
426426

427+
public Builder setProxyCredentials(String user, String pass) {
428+
this.configuration.put("proxy_user", user);
429+
this.configuration.put("proxy_password", pass);
430+
return this;
431+
}
432+
427433
/**
428434
* Sets the maximum time for operation to complete. By default, it is set to 3 hours.
429435
* @param timeout
@@ -444,6 +450,12 @@ public Builder useNewImplementation(boolean useNewImplementation) {
444450
return this;
445451
}
446452

453+
public Builder setHttpCookiesEnabled(boolean enabled) {
454+
//TODO: extract to settings string constants
455+
this.configuration.put("client.http.cookies_enabled", String.valueOf(enabled));
456+
return this;
457+
}
458+
447459
public Client build() {
448460
// check if endpoint are empty. so can not initiate client
449461
if (this.endpoints.isEmpty()) {
@@ -963,6 +975,8 @@ public CompletableFuture<QueryResponse> query(String sqlQuery, Map<String, Objec
963975
metrics.operationComplete();
964976

965977
return new QueryResponse(httpResponse, finalSettings, metrics);
978+
} catch (ClientException e) {
979+
throw e;
966980
} catch (Exception e) {
967981
throw new ClientException("Failed to execute query", e);
968982
}
@@ -1145,7 +1159,7 @@ public CompletableFuture<CommandResponse> execute(String sql) {
11451159
} catch (Exception e) {
11461160
throw new ClientException("Failed to get command response", e);
11471161
}
1148-
});
1162+
}, sharedOperationExecutor);
11491163
}
11501164

11511165
private String startOperation() {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.clickhouse.client.api;
2+
3+
/**
4+
* Represents errors caused by a client misconfiguration.
5+
*/
6+
public class ClientMisconfigurationException extends ClientException {
7+
public ClientMisconfigurationException(String message) {
8+
super(message);
9+
}
10+
11+
public ClientMisconfigurationException(String message, Throwable cause) {
12+
super(message, cause);
13+
}
14+
}

client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java

Lines changed: 67 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,20 @@
33
import com.clickhouse.client.ClickHouseNode;
44
import com.clickhouse.client.api.Client;
55
import com.clickhouse.client.api.ClientException;
6+
import com.clickhouse.client.api.ClientMisconfigurationException;
67
import com.clickhouse.client.api.ServerException;
8+
import com.clickhouse.client.api.enums.ProxyType;
79
import com.clickhouse.client.config.ClickHouseClientOption;
810
import com.clickhouse.client.http.ApacheHttpConnectionImpl;
911
import com.clickhouse.client.http.ClickHouseHttpProto;
1012
import com.clickhouse.client.http.config.ClickHouseHttpOption;
11-
import org.apache.hc.client5.http.SchemePortResolver;
1213
import org.apache.hc.client5.http.classic.methods.HttpPost;
1314
import org.apache.hc.client5.http.config.RequestConfig;
15+
import org.apache.hc.client5.http.impl.auth.CredentialsProviderBuilder;
1416
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
1517
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
1618
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
17-
import org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner;
19+
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
1820
import org.apache.hc.client5.http.protocol.HttpClientContext;
1921
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
2022
import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory;
@@ -25,6 +27,7 @@
2527
import org.apache.hc.core5.http.Header;
2628
import org.apache.hc.core5.http.HttpHeaders;
2729
import org.apache.hc.core5.http.HttpHost;
30+
import org.apache.hc.core5.http.HttpStatus;
2831
import org.apache.hc.core5.http.NoHttpResponseException;
2932
import org.apache.hc.core5.http.config.RegistryBuilder;
3033
import org.apache.hc.core5.http.io.SocketConfig;
@@ -40,12 +43,14 @@
4043
import java.io.ByteArrayOutputStream;
4144
import java.io.IOException;
4245
import java.io.OutputStream;
43-
import java.io.Serializable;
4446
import java.net.ConnectException;
47+
import java.net.InetSocketAddress;
4548
import java.net.NoRouteToHostException;
4649
import java.net.URI;
4750
import java.net.URISyntaxException;
4851
import java.net.UnknownHostException;
52+
import java.util.Base64;
53+
import java.util.EnumSet;
4954
import java.util.Map;
5055
import java.util.concurrent.TimeUnit;
5156
import java.util.function.Function;
@@ -61,39 +66,62 @@ public class HttpAPIClientHelper {
6166

6267
private RequestConfig baseRequestConfig;
6368

69+
private String proxyAuthHeaderValue;
70+
6471
public HttpAPIClientHelper(Map<String, String> configuration) {
6572
this.chConfiguration = configuration;
66-
this.httpClient = createHttpClient(configuration, null);
67-
this.baseRequestConfig = RequestConfig.custom()
68-
.setConnectionRequestTimeout(1000, TimeUnit.MILLISECONDS)
69-
.build();
73+
this.httpClient = createHttpClient();
74+
75+
RequestConfig.Builder reqConfBuilder = RequestConfig.custom();
76+
MapUtils.applyLong(chConfiguration, ClickHouseClientOption.CONNECTION_TIMEOUT.getKey(),
77+
(t) -> reqConfBuilder.setConnectionRequestTimeout(t, TimeUnit.MILLISECONDS));
78+
79+
this.baseRequestConfig = reqConfBuilder.build();
7080
}
7181

72-
public CloseableHttpClient createHttpClient(Map<String, String> chConfig, Map<String, Serializable> requestConfig) {
73-
HttpClientBuilder httpclient = HttpClientBuilder.create();
82+
public CloseableHttpClient createHttpClient() {
83+
HttpClientBuilder clientBuilder = HttpClientBuilder.create();
84+
CredentialsProviderBuilder credProviderBuilder = CredentialsProviderBuilder.create();
85+
SocketConfig.Builder soCfgBuilder = SocketConfig.custom();
86+
PoolingHttpClientConnectionManagerBuilder connMgrBuilder = PoolingHttpClientConnectionManagerBuilder.create();
87+
88+
89+
MapUtils.applyInt(chConfiguration, ClickHouseClientOption.SOCKET_TIMEOUT.getKey(),
90+
(t) -> soCfgBuilder.setSoTimeout(t, TimeUnit.MILLISECONDS));
91+
MapUtils.applyInt(chConfiguration, ClickHouseClientOption.SOCKET_RCVBUF.getKey(),
92+
soCfgBuilder::setRcvBufSize);
93+
MapUtils.applyInt(chConfiguration, ClickHouseClientOption.SOCKET_SNDBUF.getKey(),
94+
soCfgBuilder::setSndBufSize);
7495

75-
76-
String proxyHost = chConfig.get(ClickHouseClientOption.PROXY_HOST.getKey());
77-
String proxyPort = chConfig.get(ClickHouseClientOption.PROXY_PORT.getKey());
96+
String proxyHost = chConfiguration.get(ClickHouseClientOption.PROXY_HOST.getKey());
97+
String proxyPort = chConfiguration.get(ClickHouseClientOption.PROXY_PORT.getKey());
98+
HttpHost proxy = null;
7899
if (proxyHost != null && proxyPort != null) {
79-
HttpHost proxy = new HttpHost(proxyHost, Integer.parseInt(proxyPort));
80-
httpclient.setProxy(proxy);
100+
proxy = new HttpHost(proxyHost, Integer.parseInt(proxyPort));
81101
}
82102

83-
// RegistryBuilder<ConnectionSocketFactory> registryBuilder = RegistryBuilder.<ConnectionSocketFactory>create()
84-
// .register("http", socketFactory.create(c, PlainConnectionSocketFactory.class))
85-
// .register("https", socketFactory.create(c, SSLConnectionSocketFactory.class));
86103

104+
String proxyTypeVal = chConfiguration.get(ClickHouseClientOption.PROXY_TYPE.getKey());
105+
ProxyType proxyType = proxyTypeVal == null ? null : ProxyType.valueOf(proxyTypeVal);
106+
if (proxyType == ProxyType.HTTP) {
107+
clientBuilder.setProxy(proxy);
108+
if (chConfiguration.containsKey("proxy_password") && chConfiguration.containsKey("proxy_user")) {
109+
proxyAuthHeaderValue = "Basic " + Base64.getEncoder().encodeToString(
110+
(chConfiguration.get("proxy_user") + ":" + chConfiguration.get("proxy_password")).getBytes());
111+
}
112+
} else if (proxyType == ProxyType.SOCKS) {
113+
soCfgBuilder.setSocksProxyAddress(new InetSocketAddress(proxyHost, Integer.parseInt(proxyPort)));
114+
}
87115

88-
PoolingHttpClientConnectionManagerBuilder connManagerBuilder = PoolingHttpClientConnectionManagerBuilder
89-
.create()
90-
.setSSLSocketFactory(new SSLConnectionSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault(), (HostnameVerifier)
91-
(hostname, session) -> {
92-
return true;
93-
}));
116+
if (chConfiguration.getOrDefault("client.http.cookies_enabled", "true")
117+
.equalsIgnoreCase("false")) {
118+
clientBuilder.disableCookieManagement();
119+
}
120+
clientBuilder.setDefaultCredentialsProvider(credProviderBuilder.build());
94121

95-
httpclient.setConnectionManager(connManagerBuilder.build());
96-
return httpclient.build();
122+
connMgrBuilder.setDefaultSocketConfig(soCfgBuilder.build());
123+
clientBuilder.setConnectionManager(connMgrBuilder.build());
124+
return clientBuilder.build();
97125
}
98126

99127
/**
@@ -138,13 +166,19 @@ public ClassicHttpResponse executeRequest(ClickHouseNode server, Map<String, Obj
138166

139167
try {
140168
ClassicHttpResponse httpResponse = httpClient.executeOpen(null, req, context);
141-
if (httpResponse.getCode() >= 400 && httpResponse.getCode() < 500) {
169+
if (httpResponse.getCode() == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED) {
170+
throw new ClientMisconfigurationException("Proxy authentication required. Please check your proxy settings.");
171+
} else if (httpResponse.getCode() >= HttpStatus.SC_BAD_REQUEST &&
172+
httpResponse.getCode() < HttpStatus.SC_SERVER_ERROR) {
142173
try {
143174
throw readError(httpResponse);
144175
} finally {
145176
httpResponse.close();
146177
}
147-
} else if (httpResponse.getCode() >= 500) {
178+
} else if (httpResponse.getCode() == HttpStatus.SC_BAD_GATEWAY) {
179+
httpResponse.close();
180+
throw new ClientException("Server returned '502 Bad gateway'. Check network and proxy settings.");
181+
} else if (httpResponse.getCode() >= HttpStatus.SC_INTERNAL_SERVER_ERROR) {
148182
httpResponse.close();
149183
return httpResponse;
150184
}
@@ -160,6 +194,8 @@ public ClassicHttpResponse executeRequest(ClickHouseNode server, Map<String, Obj
160194
throw e;
161195
} catch (NoHttpResponseException e) {
162196
throw e;
197+
} catch (ClientException e) {
198+
throw e;
163199
} catch (Exception e) {
164200
throw new ClientException("Failed to execute request", e);
165201
}
@@ -175,6 +211,10 @@ private void addHeaders(HttpPost req, Map<String, String> chConfig, Map<String,
175211
}
176212
}
177213
req.addHeader(ClickHouseHttpProto.HEADER_DATABASE, chConfig.get(ClickHouseClientOption.DATABASE.getKey()));
214+
215+
if (proxyAuthHeaderValue != null) {
216+
req.addHeader(HttpHeaders.PROXY_AUTHORIZATION, proxyAuthHeaderValue);
217+
}
178218
}
179219
private void addQueryParams(URIBuilder req, Map<String, String> chConfig, Map<String, Object> requestConfig) {
180220
if (requestConfig != null) {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.clickhouse.client.api.internal;
2+
3+
4+
import java.util.Map;
5+
import java.util.function.Consumer;
6+
7+
/**
8+
* Collection of utility methods for working with maps.
9+
*/
10+
public class MapUtils {
11+
12+
public static void applyLong(Map<String, String> map, String key, Consumer<Long> consumer) {
13+
String val = map.get(key);
14+
if (val != null) {
15+
try {
16+
consumer.accept(Long.valueOf(val));
17+
} catch (NumberFormatException e) {
18+
throw new RuntimeException("Invalid value for key " + key + ": " + val, e);
19+
}
20+
}
21+
}
22+
public static void applyInt(Map<String, String> map, String key, Consumer<Integer> consumer) {
23+
String val = map.get(key);
24+
if (val != null) {
25+
try {
26+
consumer.accept(Integer.valueOf(val));
27+
} catch (NumberFormatException e) {
28+
throw new RuntimeException("Invalid value for key " + key + ": " + val, e);
29+
}
30+
}
31+
}
32+
}

0 commit comments

Comments
 (0)