This is an automated email from the ASF dual-hosted git repository.
jin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-hugegraph.git
The following commit(s) were added to refs/heads/master by this push:
new 6ffdd9ccb refactor(server): unify URL configs when scheme is missing
(#2944)
6ffdd9ccb is described below
commit 6ffdd9ccbf63c65e23b64396a2110319a5bff184
Author: Himanshu Verma <[email protected]>
AuthorDate: Tue Feb 3 12:41:32 2026 +0530
refactor(server): unify URL configs when scheme is missing (#2944)
- 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
* repair
---------
Co-authored-by: imbajin <[email protected]>
---
docker/configs/server1-conf/rest-server.properties | 4 +-
docker/configs/server2-conf/rest-server.properties | 4 +-
docker/configs/server3-conf/rest-server.properties | 4 +-
.../org/apache/hugegraph/config/ConfigOption.java | 16 +++
.../org/apache/hugegraph/config/HugeConfig.java | 95 +++++++++++++++-
.../org/apache/hugegraph/config/ServerOptions.java | 8 +-
.../assembly/static/conf/rest-server.properties | 4 +-
.../hugegraph/unit/config/ServerOptionsTest.java | 122 +++++++++++++++++++++
8 files changed, 244 insertions(+), 13 deletions(-)
diff --git a/docker/configs/server1-conf/rest-server.properties
b/docker/configs/server1-conf/rest-server.properties
index 1fd064d88..fce537bb1 100644
--- a/docker/configs/server1-conf/rest-server.properties
+++ b/docker/configs/server1-conf/rest-server.properties
@@ -1,7 +1,7 @@
# bind url
-restserver.url=http://127.0.0.1:8081
+restserver.url=127.0.0.1:8081
# gremlin server url, need to be consistent with host and port in
gremlin-server.yaml
-gremlinserver.url=http://127.0.0.1:8181
+gremlinserver.url=127.0.0.1:8181
graphs=./conf/graphs
diff --git a/docker/configs/server2-conf/rest-server.properties
b/docker/configs/server2-conf/rest-server.properties
index cff9405f5..0e296b17b 100644
--- a/docker/configs/server2-conf/rest-server.properties
+++ b/docker/configs/server2-conf/rest-server.properties
@@ -1,7 +1,7 @@
# bind url
-restserver.url=http://127.0.0.1:8082
+restserver.url=127.0.0.1:8082
# gremlin server url, need to be consistent with host and port in
gremlin-server.yaml
-gremlinserver.url=http://127.0.0.1:8182
+gremlinserver.url=127.0.0.1:8182
graphs=./conf/graphs
diff --git a/docker/configs/server3-conf/rest-server.properties
b/docker/configs/server3-conf/rest-server.properties
index 6c158e623..f628dc61b 100644
--- a/docker/configs/server3-conf/rest-server.properties
+++ b/docker/configs/server3-conf/rest-server.properties
@@ -1,7 +1,7 @@
# bind url
-restserver.url=http://127.0.0.1:8083
+restserver.url=127.0.0.1:8083
# gremlin server url, need to be consistent with host and port in
gremlin-server.yaml
-gremlinserver.url=http://127.0.0.1:8183
+gremlinserver.url=127.0.0.1:8183
graphs=./conf/graphs
diff --git
a/hugegraph-commons/hugegraph-common/src/main/java/org/apache/hugegraph/config/ConfigOption.java
b/hugegraph-commons/hugegraph-common/src/main/java/org/apache/hugegraph/config/ConfigOption.java
index 159f13901..cffef28f1 100644
---
a/hugegraph-commons/hugegraph-common/src/main/java/org/apache/hugegraph/config/ConfigOption.java
+++
b/hugegraph-commons/hugegraph-common/src/main/java/org/apache/hugegraph/config/ConfigOption.java
@@ -20,6 +20,22 @@ package org.apache.hugegraph.config;
import com.google.common.base.Predicate;
public class ConfigOption<T> extends TypedOption<T, T> {
+ private boolean urlNormalize = false;
+ private String defaultScheme = null;
+
+ public ConfigOption<T> withUrlNormalization(String scheme) {
+ this.urlNormalize = true;
+ this.defaultScheme = scheme;
+ return this;
+ }
+
+ public boolean needsUrlNormalization() {
+ return this.urlNormalize;
+ }
+
+ public String getDefaultScheme() {
+ return this.defaultScheme;
+ }
public ConfigOption(String name, String desc, T value) {
this(name, desc, null, value);
diff --git
a/hugegraph-commons/hugegraph-common/src/main/java/org/apache/hugegraph/config/HugeConfig.java
b/hugegraph-commons/hugegraph-common/src/main/java/org/apache/hugegraph/config/HugeConfig.java
index 483715456..9f9ecdea4 100644
---
a/hugegraph-commons/hugegraph-common/src/main/java/org/apache/hugegraph/config/HugeConfig.java
+++
b/hugegraph-commons/hugegraph-common/src/main/java/org/apache/hugegraph/config/HugeConfig.java
@@ -43,6 +43,9 @@ public class HugeConfig extends PropertiesConfiguration {
private static final Logger LOG = Log.logger(HugeConfig.class);
+ // Cache for URL normalization metadata (populated lazily per key)
+ private static final Map<String, String> URL_NORMALIZATIONS = new
HashMap<>();
+
private String configPath;
public HugeConfig(Configuration config) {
@@ -87,9 +90,17 @@ public class HugeConfig extends PropertiesConfiguration {
@SuppressWarnings("unchecked")
public <T, R> R get(TypedOption<T, R> option) {
Object value = this.getProperty(option.name());
+ boolean fromDefault = false;
+
if (value == null) {
- return option.defaultValue();
+ value = option.defaultValue();
+ fromDefault = true;
}
+
+ if (!fromDefault) {
+ value = normalizeUrlOptionIfNeeded(option.name(), value);
+ }
+
return (R) value;
}
@@ -213,4 +224,86 @@ public class HugeConfig extends PropertiesConfiguration {
e, configFile);
}
}
+
+ private static Object normalizeUrlOptionIfNeeded(String key, Object value)
{
+ if (value == null) {
+ return null;
+ }
+
+ String scheme = defaultSchemeFor(key);
+ if (scheme == null) {
+ return value;
+ }
+
+ // Normalize URL options if configured with .withUrlNormalization()
+ if (value instanceof String) {
+ String original = (String) value;
+ String normalized = prefixSchemeIfMissing(original, scheme);
+
+ if (!original.equals(normalized)) {
+ LOG.warn("Config '{}' is missing scheme, auto-corrected to
'{}'",
+ key, normalized);
+ }
+
+ return normalized;
+ }
+
+ // If it ever hits here, it means config storage returned a non-string
type;
+ // leave it unchanged (safer than forcing toString()).
+ return value;
+ }
+
+ private static String defaultSchemeFor(String key) {
+ // Check if we already cached this key's scheme
+ if (URL_NORMALIZATIONS.containsKey(key)) {
+ return URL_NORMALIZATIONS.get(key);
+ }
+
+ // We don't know yet - look it up NOW from OptionSpace
+ synchronized (URL_NORMALIZATIONS) {
+ // Double-check after acquiring lock
+ if (URL_NORMALIZATIONS.containsKey(key)) {
+ return URL_NORMALIZATIONS.get(key);
+ }
+
+ // Look up the option from OptionSpace
+ TypedOption<?, ?> option = OptionSpace.get(key);
+ String scheme = null;
+
+ if (option instanceof ConfigOption) {
+ ConfigOption<?> configOption = (ConfigOption<?>) option;
+ if (configOption.needsUrlNormalization()) {
+ scheme = configOption.getDefaultScheme();
+ }
+ }
+
+ // Cache it for next time (even if null)
+ URL_NORMALIZATIONS.put(key, scheme);
+ return scheme;
+ }
+ }
+
+ private static String prefixSchemeIfMissing(String raw, String scheme) {
+ if (raw == null) {
+ return null;
+ }
+ String s = raw.trim();
+ if (s.isEmpty()) {
+ return s;
+ }
+
+ int scIdx = s.indexOf("://");
+ if (scIdx > 0) {
+ // Normalize existing scheme to lowercase while preserving the rest
+ String existingScheme = s.substring(0, scIdx).toLowerCase();
+ String rest = s.substring(scIdx + 3); // skip the "://" delimiter
+ return existingScheme + "://" + rest;
+ }
+
+ String defaultScheme = scheme == null ? "" : scheme;
+ if (!defaultScheme.isEmpty() && !defaultScheme.endsWith("://")) {
+ defaultScheme = defaultScheme + "://";
+ }
+ return defaultScheme + s;
+ }
}
diff --git
a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java
b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java
index 920d119d4..278542854 100644
---
a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java
+++
b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java
@@ -32,7 +32,7 @@ public class ServerOptions extends OptionHolder {
"The url for listening of graph server.",
disallowEmpty(),
"http://127.0.0.1:8080"
- );
+ ).withUrlNormalization("http://");
public static final ConfigOption<Integer> SERVER_EVENT_HUB_THREADS =
new ConfigOption<>(
@@ -118,7 +118,7 @@ public class ServerOptions extends OptionHolder {
"The url of gremlin server.",
disallowEmpty(),
"http://127.0.0.1:8182"
- );
+ ).withUrlNormalization("http://");
public static final ConfigOption<Integer> GREMLIN_SERVER_TIMEOUT =
new ConfigOption<>(
@@ -270,7 +270,7 @@ public class ServerOptions extends OptionHolder {
"to clients. only used when starting the server in k8s.",
disallowEmpty(),
"http://0.0.0.0:8080"
- );
+ ).withUrlNormalization("http://");
public static final ConfigOption<String> SERVER_K8S_URL =
new ConfigOption<>(
@@ -278,7 +278,7 @@ public class ServerOptions extends OptionHolder {
"The url of k8s.",
disallowEmpty(),
"https://127.0.0.1:8888"
- );
+ ).withUrlNormalization("https://");
public static final ConfigOption<Boolean> SERVER_K8S_USE_CA =
new ConfigOption<>(
diff --git
a/hugegraph-server/hugegraph-dist/src/assembly/static/conf/rest-server.properties
b/hugegraph-server/hugegraph-dist/src/assembly/static/conf/rest-server.properties
index 0dce97271..ad3e2700f 100644
---
a/hugegraph-server/hugegraph-dist/src/assembly/static/conf/rest-server.properties
+++
b/hugegraph-server/hugegraph-dist/src/assembly/static/conf/rest-server.properties
@@ -1,9 +1,9 @@
# bind url
# could use '0.0.0.0' or specified (real)IP to expose external network access
-restserver.url=http://127.0.0.1:8080
+restserver.url=127.0.0.1:8080
#restserver.enable_graphspaces_filter=false
# gremlin server url, need to be consistent with host and port in
gremlin-server.yaml
-#gremlinserver.url=http://127.0.0.1:8182
+#gremlinserver.url=127.0.0.1:8182
graphs=./conf/graphs
diff --git
a/hugegraph-server/hugegraph-test/src/test/java/org/apache/hugegraph/unit/config/ServerOptionsTest.java
b/hugegraph-server/hugegraph-test/src/test/java/org/apache/hugegraph/unit/config/ServerOptionsTest.java
new file mode 100644
index 000000000..6113c748b
--- /dev/null
+++
b/hugegraph-server/hugegraph-test/src/test/java/org/apache/hugegraph/unit/config/ServerOptionsTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.
+ */
+
+package org.apache.hugegraph.unit.config;
+
+import org.apache.commons.configuration2.PropertiesConfiguration;
+import org.apache.hugegraph.config.HugeConfig;
+import org.apache.hugegraph.config.OptionSpace;
+import org.apache.hugegraph.config.ServerOptions;
+import org.apache.hugegraph.testutil.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ServerOptionsTest {
+
+ @BeforeClass
+ public static void init() {
+ OptionSpace.register("server",
+ ServerOptions.class.getName());
+ }
+
+ @Test
+ public void testUrlOptionNormalizeAddsDefaultScheme() {
+ PropertiesConfiguration conf = new PropertiesConfiguration();
+ conf.setProperty("restserver.url", "127.0.0.1:8080");
+ conf.setProperty("gremlinserver.url", "127.0.0.1:8182");
+ conf.setProperty("server.urls_to_pd", "0.0.0.0:8080");
+ conf.setProperty("server.k8s_url", "127.0.0.1:8888");
+
+ HugeConfig config = new HugeConfig(conf);
+
+ Assert.assertEquals("http://127.0.0.1:8080",
+ config.get(ServerOptions.REST_SERVER_URL));
+ Assert.assertEquals("http://127.0.0.1:8182",
+ config.get(ServerOptions.GREMLIN_SERVER_URL));
+ Assert.assertEquals("http://0.0.0.0:8080",
+ config.get(ServerOptions.SERVER_URLS_TO_PD));
+ Assert.assertEquals("https://127.0.0.1:8888",
+ config.get(ServerOptions.SERVER_K8S_URL));
+ }
+
+ @Test
+ public void testUrlNormalizationEdgeCases() {
+ // Whitespace trimming
+ PropertiesConfiguration conf = new PropertiesConfiguration();
+ conf.setProperty("restserver.url", " 127.0.0.1:8080 ");
+ HugeConfig config = new HugeConfig(conf);
+ Assert.assertEquals("http://127.0.0.1:8080",
+ config.get(ServerOptions.REST_SERVER_URL));
+
+ // Case normalization
+ conf = new PropertiesConfiguration();
+ conf.setProperty("restserver.url", "HTTP://127.0.0.1:8080");
+ config = new HugeConfig(conf);
+ Assert.assertEquals("http://127.0.0.1:8080",
+ config.get(ServerOptions.REST_SERVER_URL));
+
+ // IPv6 without scheme
+ conf = new PropertiesConfiguration();
+ conf.setProperty("restserver.url", "[::1]:8080");
+ config = new HugeConfig(conf);
+ Assert.assertEquals("http://[::1]:8080",
+ config.get(ServerOptions.REST_SERVER_URL));
+
+ // IPv6 with existing scheme
+ conf = new PropertiesConfiguration();
+ conf.setProperty("restserver.url", "http://[::1]:8080");
+ config = new HugeConfig(conf);
+ Assert.assertEquals("http://[::1]:8080",
+ config.get(ServerOptions.REST_SERVER_URL));
+ }
+
+ @Test
+ public void testUrlNormalizationPreservesHostnameCase() {
+ // Uppercase scheme + mixed-case hostname
+ PropertiesConfiguration conf = new PropertiesConfiguration();
+ conf.setProperty("restserver.url", "HTTP://MyServer:8080");
+ HugeConfig config = new HugeConfig(conf);
+ // Should lowercase ONLY the scheme, preserve "MyServer"
+ Assert.assertEquals("http://MyServer:8080",
+ config.get(ServerOptions.REST_SERVER_URL));
+
+ // Use server.k8s_url for HTTPS test (it defaults to https://)
+ conf = new PropertiesConfiguration();
+ conf.setProperty("server.k8s_url", "HTTPS://MyHost:8888");
+ config = new HugeConfig(conf);
+ Assert.assertEquals("https://MyHost:8888",
+ config.get(ServerOptions.SERVER_K8S_URL));
+ }
+
+ @Test
+ public void testUrlNormalizationPreservesPathCase() {
+ PropertiesConfiguration conf = new PropertiesConfiguration();
+ conf.setProperty("restserver.url",
"http://127.0.0.1:8080/SomePath/CaseSensitive");
+ HugeConfig config = new HugeConfig(conf);
+ Assert.assertEquals("http://127.0.0.1:8080/SomePath/CaseSensitive",
+ config.get(ServerOptions.REST_SERVER_URL));
+ }
+
+ @Test
+ public void testHttpsSchemeIsNotDowngraded() {
+ PropertiesConfiguration conf = new PropertiesConfiguration();
+ conf.setProperty("restserver.url", "https://127.0.0.1:8080");
+ HugeConfig config = new HugeConfig(conf);
+ Assert.assertEquals("https://127.0.0.1:8080",
+ config.get(ServerOptions.REST_SERVER_URL));
+ }
+}