Skip to content

Commit 8266a69

Browse files
Copilotchernser
andcommitted
Add custom URL path configuration support
- Added CUSTOM_URL_PATH property to ClientConfigProperties - Added customURLPath() builder method to Client.Builder - Modified HttpAPIClientHelper.executeRequest to append custom path to URLs - Added comprehensive unit tests for the new feature - All tests passing Co-authored-by: chernser <[email protected]>
1 parent 04775c1 commit 8266a69

File tree

4 files changed

+185
-0
lines changed

4 files changed

+185
-0
lines changed

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,19 @@ public Builder sslSocketSNI(String sni) {
10481048
return this;
10491049
}
10501050

1051+
/**
1052+
* Sets a custom URL path to be appended to the base URL for routing requests.
1053+
* This is useful when multiple database instances are behind a load balancer and routing
1054+
* is configured by path. For example: "/sales/db" or "/app/db".
1055+
*
1056+
* @param path - custom URL path (should start with "/")
1057+
* @return this builder instance
1058+
*/
1059+
public Builder customURLPath(String path) {
1060+
this.configuration.put(ClientConfigProperties.CUSTOM_URL_PATH.getKey(), path);
1061+
return this;
1062+
}
1063+
10511064
public Client build() {
10521065
// check if endpoint are empty. so can not initiate client
10531066
if (this.endpoints.isEmpty()) {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,12 @@ public Object parseValue(String value) {
182182
* SNI SSL parameter that will be set for each outbound SSL socket.
183183
*/
184184
SSL_SOCKET_SNI("ssl_socket_sni", String.class,""),
185+
186+
/**
187+
* Custom URL path to be appended to the base URL for routing requests.
188+
* For example: "/sales/db" or "/app/db"
189+
*/
190+
CUSTOM_URL_PATH("custom_url_path", String.class, ""),
185191
;
186192

187193
private static final Logger LOG = LoggerFactory.getLogger(ClientConfigProperties.class);

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,18 @@ public ClassicHttpResponse executeRequest(Endpoint server, Map<String, Object> r
423423
URI uri;
424424
try {
425425
URIBuilder uriBuilder = new URIBuilder(server.getBaseURL());
426+
427+
// Add custom URL path if configured
428+
String customPath = (String) requestConfig.get(ClientConfigProperties.CUSTOM_URL_PATH.getKey());
429+
if (customPath != null && !customPath.isEmpty()) {
430+
String existingPath = uriBuilder.getPath();
431+
if (existingPath == null || existingPath.isEmpty() || existingPath.equals("/")) {
432+
uriBuilder.setPath(customPath);
433+
} else {
434+
uriBuilder.setPath(existingPath + customPath);
435+
}
436+
}
437+
426438
addQueryParams(uriBuilder, requestConfig);
427439
uri = uriBuilder.normalizeSyntax().build();
428440
} catch (URISyntaxException e) {
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package com.clickhouse.client.api;
2+
3+
import org.apache.hc.core5.net.URIBuilder;
4+
import org.testng.annotations.Test;
5+
6+
import java.net.URI;
7+
import java.net.URISyntaxException;
8+
import java.util.HashMap;
9+
import java.util.Map;
10+
11+
import static org.testng.Assert.assertEquals;
12+
import static org.testng.Assert.assertTrue;
13+
14+
/**
15+
* Unit tests for custom URL path configuration feature.
16+
* Tests that custom paths are correctly appended to endpoint URLs.
17+
*/
18+
public class CustomURLPathTest {
19+
20+
@Test(groups = {"unit"})
21+
public void testCustomURLPathConfiguration() {
22+
String customPath = "/sales/db";
23+
24+
// Simulate the URL building logic from HttpAPIClientHelper
25+
String baseURL = "http://localhost:8123";
26+
Map<String, Object> requestConfig = new HashMap<>();
27+
requestConfig.put(ClientConfigProperties.CUSTOM_URL_PATH.getKey(), customPath);
28+
29+
try {
30+
URIBuilder uriBuilder = new URIBuilder(baseURL);
31+
32+
// Add custom URL path if configured
33+
String configuredPath = (String) requestConfig.get(ClientConfigProperties.CUSTOM_URL_PATH.getKey());
34+
if (configuredPath != null && !configuredPath.isEmpty()) {
35+
String existingPath = uriBuilder.getPath();
36+
if (existingPath == null || existingPath.isEmpty() || existingPath.equals("/")) {
37+
uriBuilder.setPath(configuredPath);
38+
} else {
39+
uriBuilder.setPath(existingPath + configuredPath);
40+
}
41+
}
42+
43+
URI uri = uriBuilder.normalizeSyntax().build();
44+
45+
assertEquals(uri.toString(), "http://localhost:8123/sales/db");
46+
assertEquals(uri.getPath(), "/sales/db");
47+
} catch (URISyntaxException e) {
48+
throw new RuntimeException(e);
49+
}
50+
}
51+
52+
@Test(groups = {"unit"})
53+
public void testCustomURLPathWithExistingPath() {
54+
String customPath = "/app/db";
55+
56+
// Test with base URL that already has a path
57+
String baseURL = "http://localhost:8123/api";
58+
Map<String, Object> requestConfig = new HashMap<>();
59+
requestConfig.put(ClientConfigProperties.CUSTOM_URL_PATH.getKey(), customPath);
60+
61+
try {
62+
URIBuilder uriBuilder = new URIBuilder(baseURL);
63+
64+
// Add custom URL path if configured
65+
String configuredPath = (String) requestConfig.get(ClientConfigProperties.CUSTOM_URL_PATH.getKey());
66+
if (configuredPath != null && !configuredPath.isEmpty()) {
67+
String existingPath = uriBuilder.getPath();
68+
if (existingPath == null || existingPath.isEmpty() || existingPath.equals("/")) {
69+
uriBuilder.setPath(configuredPath);
70+
} else {
71+
uriBuilder.setPath(existingPath + configuredPath);
72+
}
73+
}
74+
75+
URI uri = uriBuilder.normalizeSyntax().build();
76+
77+
assertEquals(uri.toString(), "http://localhost:8123/api/app/db");
78+
assertEquals(uri.getPath(), "/api/app/db");
79+
} catch (URISyntaxException e) {
80+
throw new RuntimeException(e);
81+
}
82+
}
83+
84+
@Test(groups = {"unit"})
85+
public void testEmptyCustomURLPath() {
86+
// Test with empty custom path
87+
String baseURL = "http://localhost:8123";
88+
Map<String, Object> requestConfig = new HashMap<>();
89+
requestConfig.put(ClientConfigProperties.CUSTOM_URL_PATH.getKey(), "");
90+
91+
try {
92+
URIBuilder uriBuilder = new URIBuilder(baseURL);
93+
94+
// Add custom URL path if configured
95+
String configuredPath = (String) requestConfig.get(ClientConfigProperties.CUSTOM_URL_PATH.getKey());
96+
if (configuredPath != null && !configuredPath.isEmpty()) {
97+
String existingPath = uriBuilder.getPath();
98+
if (existingPath == null || existingPath.isEmpty() || existingPath.equals("/")) {
99+
uriBuilder.setPath(configuredPath);
100+
} else {
101+
uriBuilder.setPath(existingPath + configuredPath);
102+
}
103+
}
104+
105+
URI uri = uriBuilder.normalizeSyntax().build();
106+
107+
// Empty path should not modify the URL
108+
assertEquals(uri.toString(), "http://localhost:8123");
109+
} catch (URISyntaxException e) {
110+
throw new RuntimeException(e);
111+
}
112+
}
113+
114+
@Test(groups = {"unit"})
115+
public void testNoCustomURLPath() {
116+
// Test without custom path configured
117+
String baseURL = "http://localhost:8123";
118+
Map<String, Object> requestConfig = new HashMap<>();
119+
120+
try {
121+
URIBuilder uriBuilder = new URIBuilder(baseURL);
122+
123+
// Add custom URL path if configured
124+
String configuredPath = (String) requestConfig.get(ClientConfigProperties.CUSTOM_URL_PATH.getKey());
125+
if (configuredPath != null && !configuredPath.isEmpty()) {
126+
String existingPath = uriBuilder.getPath();
127+
if (existingPath == null || existingPath.isEmpty() || existingPath.equals("/")) {
128+
uriBuilder.setPath(configuredPath);
129+
} else {
130+
uriBuilder.setPath(existingPath + configuredPath);
131+
}
132+
}
133+
134+
URI uri = uriBuilder.normalizeSyntax().build();
135+
136+
// No custom path should keep URL unchanged
137+
assertEquals(uri.toString(), "http://localhost:8123");
138+
} catch (URISyntaxException e) {
139+
throw new RuntimeException(e);
140+
}
141+
}
142+
143+
@Test(groups = {"unit"})
144+
public void testClientBuilderCustomURLPath() {
145+
// Test that the builder method sets the configuration correctly
146+
Map<String, String> config = new HashMap<>();
147+
config.put(ClientConfigProperties.CUSTOM_URL_PATH.getKey(), "/sales/db");
148+
149+
Map<String, Object> parsedConfig = ClientConfigProperties.parseConfigMap(config);
150+
151+
String customPath = (String) parsedConfig.get(ClientConfigProperties.CUSTOM_URL_PATH.getKey());
152+
assertEquals(customPath, "/sales/db");
153+
}
154+
}

0 commit comments

Comments
 (0)