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));
+    }
+}

Reply via email to