Skip to content

Commit d1de457

Browse files
committed
feat: add URL normalization with warning logs
- Add URL normalization support for config options - Automatically prefix missing schemes (http://, https://) - Log warnings when auto-correcting user-provided values - Add comprehensive test coverage for normalization logic - Update config files to demonstrate the feature Changes: - ConfigOption: Add withUrlNormalization() builder method - ServerOptions: Apply normalization to REST, Gremlin, K8s URLs - HugeConfig: Implement lazy cache and normalization logic - Add ServerOptionsTest with 5 test cases - Simplify URLs in main and Docker config
1 parent 09c4fc0 commit d1de457

File tree

6 files changed

+102
-22
lines changed

6 files changed

+102
-22
lines changed

docker/configs/server1-conf/rest-server.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# bind url
2-
restserver.url=http://127.0.0.1:8081
2+
restserver.url=127.0.0.1:8081
33
# gremlin server url, need to be consistent with host and port in gremlin-server.yaml
4-
gremlinserver.url=http://127.0.0.1:8181
4+
gremlinserver.url=127.0.0.1:8181
55

66
graphs=./conf/graphs
77

docker/configs/server2-conf/rest-server.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# bind url
2-
restserver.url=http://127.0.0.1:8082
2+
restserver.url=127.0.0.1:8082
33
# gremlin server url, need to be consistent with host and port in gremlin-server.yaml
4-
gremlinserver.url=http://127.0.0.1:8182
4+
gremlinserver.url=127.0.0.1:8182
55

66
graphs=./conf/graphs
77

docker/configs/server3-conf/rest-server.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# bind url
2-
restserver.url=http://127.0.0.1:8083
2+
restserver.url=127.0.0.1:8083
33
# gremlin server url, need to be consistent with host and port in gremlin-server.yaml
4-
gremlinserver.url=http://127.0.0.1:8183
4+
gremlinserver.url=127.0.0.1:8183
55

66
graphs=./conf/graphs
77

hugegraph-commons/hugegraph-common/src/main/java/org/apache/hugegraph/config/HugeConfig.java

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ public class HugeConfig extends PropertiesConfiguration {
4343

4444
private static final Logger LOG = Log.logger(HugeConfig.class);
4545

46+
// Cache for url normalization metadata
47+
// Populated lazily on first use to ensure OptionSpace is already registered
48+
private static final Map<String, String> URL_NORMALIZATIONS = new HashMap<>();
49+
private static volatile boolean cacheInitialized = false;
50+
4651
private String configPath;
4752

4853
public HugeConfig(Configuration config) {
@@ -232,10 +237,17 @@ private static Object normalizeUrlOptionIfNeeded(String key, Object value) {
232237
return value;
233238
}
234239

235-
// Normalize URL config values by adding default scheme if missing.
236-
// Only applies to allowlisted URL options (see defaultSchemeFor()).
240+
// Normalize URL options if configured with .withUrlNormalization()
237241
if (value instanceof String) {
238-
return prefixSchemeIfMissing((String) value, scheme);
242+
String original = (String) value;
243+
String normalized = prefixSchemeIfMissing(original, scheme);
244+
245+
if (!original.equals(normalized)) {
246+
LOG.warn("Config '{}' is missing scheme, auto-corrected to '{}'",
247+
key, normalized);
248+
}
249+
250+
return normalized;
239251
}
240252

241253
// If it ever hits here, it means config storage returned a non-string type;
@@ -244,14 +256,29 @@ private static Object normalizeUrlOptionIfNeeded(String key, Object value) {
244256
}
245257

246258
private static String defaultSchemeFor(String key) {
247-
TypedOption<?, ?> option = OptionSpace.get(key);
248-
if (option instanceof ConfigOption) {
249-
ConfigOption<?> configOption = (ConfigOption<?>) option;
250-
if (configOption.needsUrlNormalization()) {
251-
return configOption.getDefaultScheme();
259+
ensureCacheInitialized();
260+
return URL_NORMALIZATIONS.get(key);
261+
}
262+
263+
private static void ensureCacheInitialized() {
264+
if (!cacheInitialized) {
265+
synchronized (URL_NORMALIZATIONS) {
266+
if (!cacheInitialized) {
267+
// Populate cache from OptionSpace
268+
for (String optionKey : OptionSpace.keys()) {
269+
TypedOption<?, ?> option = OptionSpace.get(optionKey);
270+
if (option instanceof ConfigOption) {
271+
ConfigOption<?> configOption = (ConfigOption<?>) option;
272+
if (configOption.needsUrlNormalization()) {
273+
URL_NORMALIZATIONS.put(optionKey,
274+
configOption.getDefaultScheme());
275+
}
276+
}
277+
}
278+
cacheInitialized = true;
279+
}
252280
}
253281
}
254-
return null;
255282
}
256283

257284
private static String prefixSchemeIfMissing(String raw, String scheme) {
@@ -263,11 +290,18 @@ private static String prefixSchemeIfMissing(String raw, String scheme) {
263290
return s;
264291
}
265292

266-
// Keep original string if scheme already exists
267-
String lower = s.toLowerCase();
268-
if (lower.startsWith("http://") || lower.startsWith("https://")) {
269-
return lower; // Return LOWERCASE version
293+
int scIdx = s.indexOf("://");
294+
if (scIdx > 0) {
295+
// Normalize existing scheme to lowercase while preserving the rest
296+
String existingScheme = s.substring(0, scIdx).toLowerCase();
297+
String rest = s.substring(scIdx + 3); // skip the "://" delimiter
298+
return existingScheme + "://" + rest;
299+
}
300+
301+
String defaultScheme = scheme == null ? "" : scheme;
302+
if (!defaultScheme.isEmpty() && !defaultScheme.endsWith("://")) {
303+
defaultScheme = defaultScheme + "://";
270304
}
271-
return scheme + lower; // Return scheme + LOWERCASE input
305+
return defaultScheme + s;
272306
}
273307
}

hugegraph-server/hugegraph-dist/src/assembly/static/conf/rest-server.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# bind url
22
# could use '0.0.0.0' or specified (real)IP to expose external network access
3-
restserver.url=http://127.0.0.1:8080
3+
restserver.url=127.0.0.1:8080
44
#restserver.enable_graphspaces_filter=false
55
# gremlin server url, need to be consistent with host and port in gremlin-server.yaml
6-
#gremlinserver.url=http://127.0.0.1:8182
6+
#gremlinserver.url=127.0.0.1:8182
77

88
graphs=./conf/graphs
99

hugegraph-server/hugegraph-test/src/test/java/org/apache/hugegraph/unit/config/ServerOptionsTest.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,22 +55,68 @@ public void testUrlOptionNormalizeAddsDefaultScheme() {
5555

5656
@Test
5757
public void testUrlNormalizationEdgeCases() {
58+
// Whitespace trimming
5859
PropertiesConfiguration conf = new PropertiesConfiguration();
5960
conf.setProperty("restserver.url", " 127.0.0.1:8080 ");
6061
HugeConfig config = new HugeConfig(conf);
6162
Assert.assertEquals("http://127.0.0.1:8080",
6263
config.get(ServerOptions.REST_SERVER_URL));
6364

65+
// Case normalization
6466
conf = new PropertiesConfiguration();
6567
conf.setProperty("restserver.url", "HTTP://127.0.0.1:8080");
6668
config = new HugeConfig(conf);
6769
Assert.assertEquals("http://127.0.0.1:8080",
6870
config.get(ServerOptions.REST_SERVER_URL));
6971

72+
// IPv6 without scheme
7073
conf = new PropertiesConfiguration();
7174
conf.setProperty("restserver.url", "[::1]:8080");
7275
config = new HugeConfig(conf);
7376
Assert.assertEquals("http://[::1]:8080",
7477
config.get(ServerOptions.REST_SERVER_URL));
78+
79+
// IPv6 with existing scheme
80+
conf = new PropertiesConfiguration();
81+
conf.setProperty("restserver.url", "http://[::1]:8080");
82+
config = new HugeConfig(conf);
83+
Assert.assertEquals("http://[::1]:8080",
84+
config.get(ServerOptions.REST_SERVER_URL));
85+
}
86+
87+
@Test
88+
public void testUrlNormalizationPreservesHostnameCase() {
89+
// Uppercase scheme + mixed-case hostname
90+
PropertiesConfiguration conf = new PropertiesConfiguration();
91+
conf.setProperty("restserver.url", "HTTP://MyServer:8080");
92+
HugeConfig config = new HugeConfig(conf);
93+
// Should lowercase ONLY the scheme, preserve "MyServer"
94+
Assert.assertEquals("http://MyServer:8080",
95+
config.get(ServerOptions.REST_SERVER_URL));
96+
97+
// Use server.k8s_url for HTTPS test (it defaults to https://)
98+
conf = new PropertiesConfiguration();
99+
conf.setProperty("server.k8s_url", "HTTPS://MyHost:8888");
100+
config = new HugeConfig(conf);
101+
Assert.assertEquals("https://MyHost:8888",
102+
config.get(ServerOptions.SERVER_K8S_URL));
103+
}
104+
105+
@Test
106+
public void testUrlNormalizationPreservesPathCase() {
107+
PropertiesConfiguration conf = new PropertiesConfiguration();
108+
conf.setProperty("restserver.url", "http://127.0.0.1:8080/SomePath/CaseSensitive");
109+
HugeConfig config = new HugeConfig(conf);
110+
Assert.assertEquals("http://127.0.0.1:8080/SomePath/CaseSensitive",
111+
config.get(ServerOptions.REST_SERVER_URL));
112+
}
113+
114+
@Test
115+
public void testHttpsSchemeIsNotDowngraded() {
116+
PropertiesConfiguration conf = new PropertiesConfiguration();
117+
conf.setProperty("restserver.url", "https://127.0.0.1:8080");
118+
HugeConfig config = new HugeConfig(conf);
119+
Assert.assertEquals("https://127.0.0.1:8080",
120+
config.get(ServerOptions.REST_SERVER_URL));
75121
}
76122
}

0 commit comments

Comments
 (0)