This is an automated email from the ASF dual-hosted git repository.

jerryshao pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-1.3 by this push:
     new 544cf8a5df [Cherry-pick to branch-1.3] [MINOR] refactor(server): Share 
Iceberg REST service URI resolution (#13738) (#13742)
544cf8a5df is described below

commit 544cf8a5dfd1b595e67c130379920db6b8a3e594
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Sat Oct 10 18:47:15 2026 +0800

    [Cherry-pick to branch-1.3] [MINOR] refactor(server): Share Iceberg REST 
service URI resolution (#13738) (#13742)
    
    **Cherry-pick Information:**
    - Original commit: ddbc9a20c7258ff81b5d6195382434ecf81f4db5
    - Target branch: `branch-1.3`
    - Status: ✅ Clean cherry-pick (no conflicts)
    
    Co-authored-by: mchades <[email protected]>
---
 .../org/apache/gravitino/server/web/Utils.java     | 161 +++++++++++++++++++++
 .../org/apache/gravitino/server/web/TestUtils.java | 155 ++++++++++++++++++++
 .../web/rest/IcebergRESTServiceOperations.java     | 149 +------------------
 3 files changed, 323 insertions(+), 142 deletions(-)

diff --git 
a/server-common/src/main/java/org/apache/gravitino/server/web/Utils.java 
b/server-common/src/main/java/org/apache/gravitino/server/web/Utils.java
index 43e8d41568..3b03ae88fa 100644
--- a/server-common/src/main/java/org/apache/gravitino/server/web/Utils.java
+++ b/server-common/src/main/java/org/apache/gravitino/server/web/Utils.java
@@ -20,11 +20,14 @@ package org.apache.gravitino.server.web;
 
 import com.google.common.collect.Maps;
 import java.lang.reflect.Parameter;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.security.PrivilegedExceptionAction;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
+import javax.annotation.Nullable;
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.core.MediaType;
@@ -37,16 +40,45 @@ import org.apache.gravitino.audit.FilesetAuditConstants;
 import org.apache.gravitino.audit.FilesetDataOperation;
 import org.apache.gravitino.audit.InternalClientType;
 import org.apache.gravitino.auth.AuthConstants;
+import org.apache.gravitino.auxiliary.AuxiliaryServiceManager;
 import org.apache.gravitino.credential.CredentialConstants;
 import org.apache.gravitino.dto.HealthCheckDTO;
 import org.apache.gravitino.dto.responses.ErrorResponse;
 import org.apache.gravitino.dto.responses.HealthResponse;
 import org.apache.gravitino.utils.PrincipalUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class Utils {
 
+  private static final Logger LOG = LoggerFactory.getLogger(Utils.class);
   private static final String REMOTE_USER = "gravitino";
 
+  // Matches gravitino.auxService.names / AuxiliaryServiceManager's 
registration key.
+  private static final String ICEBERG_REST_SERVICE_NAME = "iceberg-rest";
+  // Keys below are read from AuxiliaryServiceManager.getAuxServiceConfig, 
which already strips
+  // the gravitino.iceberg-rest. (or deprecated 
gravitino.auxService.iceberg-rest.) prefix, so
+  // they must NOT be re-prefixed here.
+  // The provider name used by the Iceberg REST server itself; see
+  // IcebergConstants.ICEBERG_REST_CATALOG_CONFIG_PROVIDER and 
DynamicIcebergConfigProvider. The
+  // shared server utilities do not depend on Iceberg implementation modules.
+  private static final String ICEBERG_CATALOG_CONFIG_PROVIDER_KEY = 
"catalog-config-provider";
+  private static final String ICEBERG_DYNAMIC_CONFIG_PROVIDER_NAME = 
"dynamic-config-provider";
+  // The post-strip key used by the Iceberg REST server itself; see
+  // IcebergConstants.GRAVITINO_METALAKE and DynamicIcebergConfigProvider.
+  private static final String ICEBERG_SERVED_METALAKE_KEY = 
"gravitino-metalake";
+  // Overrides the listener-derived endpoint; see docs/iceberg-rest-service.md.
+  private static final String ICEBERG_ADVERTISED_URI_KEY = "advertised-uri";
+  private static final String ICEBERG_HOST_KEY = "host";
+  private static final String ICEBERG_HTTP_PORT_KEY = "httpPort";
+  private static final String ICEBERG_HTTPS_PORT_KEY = "httpsPort";
+  private static final String ICEBERG_ENABLE_HTTPS_KEY = "enableHttps";
+  // Match IcebergConfig.DEFAULT_ICEBERG_REST_SERVICE_HTTP_PORT/HTTPS_PORT. 
JettyServerConfig's
+  // defaults belong to the Gravitino server (8090/8433), not the Iceberg REST 
server.
+  private static final int ICEBERG_DEFAULT_HTTP_PORT = 9001;
+  private static final int ICEBERG_DEFAULT_HTTPS_PORT = 9433;
+  private static final String ICEBERG_DEFAULT_HOST = "0.0.0.0";
+
   private Utils() {}
 
   public static String remoteUser(HttpServletRequest httpRequest) {
@@ -340,4 +372,133 @@ public class Utils {
     }
     return pathParams;
   }
+
+  /**
+   * Resolves the Iceberg REST service URI from the supplied service 
registration and configuration.
+   *
+   * <p>A valid advertised URI takes precedence over the listener 
configuration. Otherwise, the URI
+   * uses the configured host, protocol and port, with Iceberg REST defaults 
for omitted values. A
+   * wildcard listener host is replaced with {@code requestHost}, assuming 
both services share a
+   * host. Reverse proxies or split deployments should configure {@code 
advertised-uri} explicitly.
+   *
+   * @param serviceManager the auxiliary service manager used to check service 
registration
+   * @param serviceConfig the effective Iceberg REST service configuration 
returned by {@link
+   *     AuxiliaryServiceManager#getAuxServiceConfig}, with the service prefix 
stripped
+   * @param metalake the requested metalake, or null/blank to skip the 
metalake check
+   * @param requestHost the hostname from the current request, used for 
wildcard listener hosts
+   * @return the service URI, or null if the service is not registered, does 
not use the dynamic
+   *     catalog config provider, or serves a different metalake
+   * @throws IllegalStateException if the advertised URI is invalid
+   */
+  @Nullable
+  public static String resolveIcebergRestServiceUri(
+      AuxiliaryServiceManager serviceManager,
+      Map<String, String> serviceConfig,
+      @Nullable String metalake,
+      String requestHost) {
+    if (!serviceManager.isAuxServiceRegistered(ICEBERG_REST_SERVICE_NAME)) {
+      return null;
+    }
+
+    String provider = 
serviceConfig.getOrDefault(ICEBERG_CATALOG_CONFIG_PROVIDER_KEY, "");
+    if (!ICEBERG_DYNAMIC_CONFIG_PROVIDER_NAME.equals(provider)) {
+      // Only the dynamic catalog config provider maps Iceberg REST catalog 
names onto Gravitino
+      // catalogs; the default static provider serves statically-declared 
catalogs unrelated to
+      // Gravitino catalog names, so routing at it would 404 on every request.
+      LOG.debug(
+          "Iceberg REST service does not use the dynamic catalog config 
provider "
+              + "(catalog-config-provider={}); not reporting its endpoint for 
auto-discovery.",
+          provider);
+      return null;
+    }
+
+    String servedMetalake = 
serviceConfig.getOrDefault(ICEBERG_SERVED_METALAKE_KEY, "");
+    if (StringUtils.isNotBlank(metalake)
+        && StringUtils.isNotBlank(servedMetalake)
+        && !servedMetalake.equals(metalake)) {
+      // The Iceberg REST server serves exactly one metalake. Routing a 
different metalake's
+      // catalogs at it would 404 on every request, so report it as 
unavailable instead.
+      LOG.debug(
+          "Iceberg REST service serves metalake {}, not the requested metalake 
{}; not "
+              + "reporting its endpoint for auto-discovery.",
+          servedMetalake,
+          metalake);
+      return null;
+    }
+
+    String advertisedUri = 
StringUtils.trimToNull(serviceConfig.get(ICEBERG_ADVERTISED_URI_KEY));
+    if (advertisedUri != null) {
+      return checkIcebergRestAdvertisedUri(advertisedUri);
+    }
+
+    String host = serviceConfig.getOrDefault(ICEBERG_HOST_KEY, 
ICEBERG_DEFAULT_HOST);
+    if (isWildcardHost(host)) {
+      host = requestHost;
+    }
+    boolean enableHttps =
+        
Boolean.parseBoolean(serviceConfig.getOrDefault(ICEBERG_ENABLE_HTTPS_KEY, 
"false"));
+    String scheme = enableHttps ? "https" : "http";
+    int port =
+        parseIcebergRestPort(
+            serviceConfig,
+            enableHttps ? ICEBERG_HTTPS_PORT_KEY : ICEBERG_HTTP_PORT_KEY,
+            enableHttps ? ICEBERG_DEFAULT_HTTPS_PORT : 
ICEBERG_DEFAULT_HTTP_PORT);
+    return String.format("%s://%s:%d/iceberg", scheme, bracketIfIPv6(host), 
port);
+  }
+
+  private static String checkIcebergRestAdvertisedUri(String value) {
+    boolean valid;
+    try {
+      URI uri = new URI(value);
+      // URI accepts any non-negative integer as a port; -1 means no explicit 
port.
+      int port = uri.getPort();
+      valid =
+          StringUtils.equalsAnyIgnoreCase(uri.getScheme(), "http", "https")
+              && StringUtils.isNotBlank(uri.getHost())
+              && (port == -1 || (port >= 1 && port <= 65535))
+              && uri.getQuery() == null
+              && uri.getFragment() == null;
+    } catch (URISyntaxException e) {
+      valid = false;
+    }
+    if (!valid) {
+      throw new IllegalStateException(
+          String.format(
+              "Invalid Iceberg REST service %s '%s': expected an absolute 
http(s) URI with a "
+                  + "host, a port in 1-65535 if present, and no query or 
fragment",
+              ICEBERG_ADVERTISED_URI_KEY, value));
+    }
+    return value;
+  }
+
+  // An IPv6 literal host (e.g. "::1", from an explicit config value or from
+  // HttpServletRequest#getServerName()) must be bracketed to form a valid URI 
authority;
+  // otherwise its colons are parsed as the port separator. A hostname or IPv4 
address never
+  // contains a colon, so this only ever fires for IPv6.
+  private static String bracketIfIPv6(String host) {
+    if (host.contains(":") && !host.startsWith("[")) {
+      return "[" + host + "]";
+    }
+    return host;
+  }
+
+  private static int parseIcebergRestPort(Map<String, String> config, String 
key, int defaultPort) {
+    String value = config.getOrDefault(key, "");
+    if (StringUtils.isBlank(value)) {
+      return defaultPort;
+    }
+    try {
+      return Integer.parseInt(value.trim());
+    } catch (NumberFormatException e) {
+      return defaultPort;
+    }
+  }
+
+  private static boolean isWildcardHost(String host) {
+    return StringUtils.isBlank(host)
+        || "0.0.0.0".equals(host)
+        || "::".equals(host)
+        || "[::]".equals(host)
+        || "0:0:0:0:0:0:0:0".equals(host);
+  }
 }
diff --git 
a/server-common/src/test/java/org/apache/gravitino/server/web/TestUtils.java 
b/server-common/src/test/java/org/apache/gravitino/server/web/TestUtils.java
index 0e8d5023b3..877a727f86 100644
--- a/server-common/src/test/java/org/apache/gravitino/server/web/TestUtils.java
+++ b/server-common/src/test/java/org/apache/gravitino/server/web/TestUtils.java
@@ -20,6 +20,8 @@ package org.apache.gravitino.server.web;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -30,11 +32,14 @@ import javax.ws.rs.core.Response;
 import org.apache.gravitino.audit.FilesetAuditConstants;
 import org.apache.gravitino.audit.FilesetDataOperation;
 import org.apache.gravitino.audit.InternalClientType;
+import org.apache.gravitino.auxiliary.AuxiliaryServiceManager;
 import org.apache.gravitino.dto.responses.ErrorConstants;
 import org.apache.gravitino.dto.responses.ErrorResponse;
 import org.apache.gravitino.exceptions.UnmodifiableStatisticException;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 import org.mockito.Mockito;
 
 /*
@@ -236,4 +241,154 @@ public class TestUtils {
         FilesetDataOperation.GET_FILE_STATUS.name(),
         
filteredMap.get(FilesetAuditConstants.HTTP_HEADER_FILESET_DATA_OPERATION));
   }
+
+  @Test
+  void testIcebergRestServiceUriRequiresRegisteredService() {
+    assertNull(
+        Utils.resolveIcebergRestServiceUri(
+            mockAuxServiceManager(false),
+            Map.of(
+                "catalog-config-provider", "dynamic-config-provider",
+                "advertised-uri", "invalid"),
+            "test",
+            "gravitino-host"));
+  }
+
+  @ParameterizedTest
+  @ValueSource(strings = {"", "static-config-provider"})
+  void testIcebergRestServiceUriRequiresDynamicProvider(String provider) {
+    assertNull(
+        Utils.resolveIcebergRestServiceUri(
+            mockAuxServiceManager(true),
+            Map.of("catalog-config-provider", provider, "advertised-uri", 
"invalid"),
+            "test",
+            "gravitino-host"));
+  }
+
+  @Test
+  void testIcebergRestServiceUriMatchesRequestedMetalake() {
+    AuxiliaryServiceManager serviceManager = mockAuxServiceManager(true);
+    String advertisedUri = "https://iceberg.example.com/proxy/iceberg/";;
+    Map<String, String> config =
+        Map.of(
+            "catalog-config-provider", "dynamic-config-provider",
+            "gravitino-metalake", "prod",
+            "advertised-uri", advertisedUri);
+
+    assertNull(
+        Utils.resolveIcebergRestServiceUri(serviceManager, config, "test", 
"gravitino-host"));
+    assertEquals(
+        advertisedUri,
+        Utils.resolveIcebergRestServiceUri(serviceManager, config, "prod", 
"gravitino-host"));
+    assertEquals(
+        advertisedUri,
+        Utils.resolveIcebergRestServiceUri(serviceManager, config, null, 
"gravitino-host"));
+    assertEquals(
+        advertisedUri,
+        Utils.resolveIcebergRestServiceUri(serviceManager, config, "", 
"gravitino-host"));
+  }
+
+  @ParameterizedTest
+  @ValueSource(strings = {"", " ", "0.0.0.0", "::", "[::]", "0:0:0:0:0:0:0:0"})
+  void testIcebergRestServiceUriUsesProvidedRequestHost(String listenerHost) {
+    assertEquals(
+        "http://[::1]:9001/iceberg";,
+        Utils.resolveIcebergRestServiceUri(
+            mockAuxServiceManager(true),
+            Map.of("catalog-config-provider", "dynamic-config-provider", 
"host", listenerHost),
+            "test",
+            "::1"));
+  }
+
+  @Test
+  void testIcebergRestServiceUriUsesListenerConfiguration() {
+    assertEquals(
+        "https://iceberg-host:19433/iceberg";,
+        Utils.resolveIcebergRestServiceUri(
+            mockAuxServiceManager(true),
+            Map.of(
+                "catalog-config-provider", "dynamic-config-provider",
+                "host", "iceberg-host",
+                "enableHttps", "true",
+                "httpPort", "19001",
+                "httpsPort", "19433"),
+            "test",
+            "gravitino-host"));
+  }
+
+  @ParameterizedTest
+  @ValueSource(booleans = {false, true})
+  void testIcebergRestServiceUriUsesDefaultPorts(boolean enableHttps) {
+    assertEquals(
+        enableHttps ? "https://gravitino-host:9433/iceberg"; : 
"http://gravitino-host:9001/iceberg";,
+        Utils.resolveIcebergRestServiceUri(
+            mockAuxServiceManager(true),
+            Map.of(
+                "catalog-config-provider",
+                "dynamic-config-provider",
+                "enableHttps",
+                Boolean.toString(enableHttps)),
+            "test",
+            "gravitino-host"));
+  }
+
+  @Test
+  void testIcebergRestServiceUriPrefersAdvertisedUri() {
+    assertEquals(
+        "https://iceberg.example.com:8443/proxy/iceberg/";,
+        Utils.resolveIcebergRestServiceUri(
+            mockAuxServiceManager(true),
+            Map.of(
+                "catalog-config-provider", "dynamic-config-provider",
+                "host", "iceberg-host",
+                "httpPort", "19001",
+                "advertised-uri", " 
https://iceberg.example.com:8443/proxy/iceberg/ "),
+            "test",
+            "gravitino-host"));
+  }
+
+  @ParameterizedTest
+  @ValueSource(
+      strings = {
+        "iceberg.example.com/iceberg",
+        "ftp://iceberg.example.com/iceberg";,
+        "https:///iceberg";,
+        "https://iceberg.example.com/iceberg?x=1";,
+        "https://iceberg.example.com/iceberg#frag";,
+        "https://iceberg.example.com:0/iceberg";,
+        "https://iceberg.example.com:70000/iceberg";,
+        "http://bad host/iceberg"
+      })
+  void testIcebergRestServiceUriRejectsInvalidAdvertisedUri(String 
advertisedUri) {
+    assertThrows(
+        IllegalStateException.class,
+        () ->
+            Utils.resolveIcebergRestServiceUri(
+                mockAuxServiceManager(true),
+                Map.of(
+                    "catalog-config-provider",
+                    "dynamic-config-provider",
+                    "advertised-uri",
+                    advertisedUri),
+                "test",
+                "gravitino-host"));
+  }
+
+  @ParameterizedTest
+  @ValueSource(strings = {"", " ", "not-a-number"})
+  void testIcebergRestServiceUriUsesDefaultForInvalidPort(String port) {
+    assertEquals(
+        "http://gravitino-host:9001/iceberg";,
+        Utils.resolveIcebergRestServiceUri(
+            mockAuxServiceManager(true),
+            Map.of("catalog-config-provider", "dynamic-config-provider", 
"httpPort", port),
+            "test",
+            "gravitino-host"));
+  }
+
+  private static AuxiliaryServiceManager mockAuxServiceManager(boolean 
registered) {
+    AuxiliaryServiceManager serviceManager = 
mock(AuxiliaryServiceManager.class);
+    
when(serviceManager.isAuxServiceRegistered("iceberg-rest")).thenReturn(registered);
+    return serviceManager;
+  }
 }
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/IcebergRESTServiceOperations.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/IcebergRESTServiceOperations.java
index ce10a056e4..5c94c6b4eb 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/IcebergRESTServiceOperations.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/IcebergRESTServiceOperations.java
@@ -20,8 +20,6 @@ package org.apache.gravitino.server.web.rest;
 
 import com.codahale.metrics.annotation.ResponseMetered;
 import com.codahale.metrics.annotation.Timed;
-import java.net.URI;
-import java.net.URISyntaxException;
 import java.util.Map;
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.Consumes;
@@ -32,7 +30,6 @@ import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-import org.apache.commons.lang3.StringUtils;
 import org.apache.gravitino.GravitinoEnv;
 import org.apache.gravitino.auxiliary.AuxiliaryServiceManager;
 import org.apache.gravitino.dto.responses.IcebergRESTServiceResponse;
@@ -52,32 +49,7 @@ public class IcebergRESTServiceOperations {
 
   private static final Logger LOG = 
LoggerFactory.getLogger(IcebergRESTServiceOperations.class);
 
-  // Matches gravitino.auxService.names / AuxiliaryServiceManager's 
registration key.
   private static final String AUX_SERVICE_NAME = "iceberg-rest";
-  // Keys below are read from AuxiliaryServiceManager.getAuxServiceConfig, 
which already strips
-  // the gravitino.iceberg-rest. (or deprecated 
gravitino.auxService.iceberg-rest.) prefix, so
-  // they must NOT be re-prefixed here.
-  // The provider name used by the Iceberg REST server itself; see
-  // IcebergConstants.ICEBERG_REST_CATALOG_CONFIG_PROVIDER and 
DynamicIcebergConfigProvider. The
-  // server module cannot depend on iceberg-common/catalog-common, hence the 
literal here.
-  private static final String CATALOG_CONFIG_PROVIDER_KEY = 
"catalog-config-provider";
-  private static final String DYNAMIC_CATALOG_CONFIG_PROVIDER_NAME = 
"dynamic-config-provider";
-  // The post-strip key used by the Iceberg REST server itself; see
-  // IcebergConstants.GRAVITINO_METALAKE and DynamicIcebergConfigProvider.
-  private static final String SERVED_METALAKE_KEY = "gravitino-metalake";
-  // Overrides the listener-derived endpoint; see docs/iceberg-rest-service.md.
-  private static final String ADVERTISED_URI_KEY = "advertised-uri";
-  private static final String HOST_KEY = "host";
-  private static final String HTTP_PORT_KEY = "httpPort";
-  private static final String HTTPS_PORT_KEY = "httpsPort";
-  private static final String ENABLE_HTTPS_KEY = "enableHttps";
-  // Match IcebergConfig.DEFAULT_ICEBERG_REST_SERVICE_HTTP_PORT/HTTPS_PORT: 
the server module
-  // cannot depend on iceberg-common, and JettyServerConfig's own defaults are 
the Gravitino
-  // server's (8090/8433), not the Iceberg REST server's — reading raw values 
with these
-  // defaults avoids silently reporting the wrong port when httpPort is not 
set explicitly.
-  private static final int DEFAULT_HTTP_PORT = 9001;
-  private static final int DEFAULT_HTTPS_PORT = 9433;
-  private static final String DEFAULT_HOST = "0.0.0.0";
 
   @Context private HttpServletRequest httpRequest;
 
@@ -96,14 +68,19 @@ public class IcebergRESTServiceOperations {
   public Response getIcebergRestServiceUri(@QueryParam("metalake") String 
metalake) {
     String uri;
     try {
-      uri = resolveUri(metalake);
+      uri =
+          Utils.resolveIcebergRestServiceUri(
+              getAuxServiceManager(),
+              getIcebergRestServiceConfig(),
+              metalake,
+              getHttpRequest().getServerName());
     } catch (IllegalStateException e) {
       // A misconfiguration, re-reported on every discovery poll until fixed; 
the message alone
       // identifies it, so the stack trace is omitted from both the log and 
the response.
       LOG.error("Failed to resolve the Iceberg REST service endpoint: {}", 
e.getMessage());
       return Utils.internalError(e.getMessage());
     }
-    // The reported host can depend on the caller's own Host header (see 
resolveUri), so this
+    // The reported host can depend on the caller's own Host header, so this
     // response must never be cached and replayed to a different caller.
     return Response.fromResponse(Utils.ok(new IcebergRESTServiceResponse(uri)))
         .header("Cache-Control", "no-store")
@@ -127,116 +104,4 @@ public class IcebergRESTServiceOperations {
   HttpServletRequest getHttpRequest() {
     return httpRequest;
   }
-
-  private String resolveUri(String metalake) {
-    if (!getAuxServiceManager().isAuxServiceRegistered(AUX_SERVICE_NAME)) {
-      return null;
-    }
-
-    Map<String, String> config = getIcebergRestServiceConfig();
-    String provider = config.getOrDefault(CATALOG_CONFIG_PROVIDER_KEY, "");
-    if (!DYNAMIC_CATALOG_CONFIG_PROVIDER_NAME.equals(provider)) {
-      // Only the dynamic catalog config provider maps Iceberg REST catalog 
names onto Gravitino
-      // catalogs; the default static provider serves statically-declared 
catalogs unrelated to
-      // Gravitino catalog names, so routing at it would 404 on every request.
-      LOG.debug(
-          "Iceberg REST service does not use the dynamic catalog config 
provider "
-              + "(catalog-config-provider={}); not reporting its endpoint for 
auto-discovery.",
-          provider);
-      return null;
-    }
-
-    String servedMetalake = config.getOrDefault(SERVED_METALAKE_KEY, "");
-    if (StringUtils.isNotBlank(metalake)
-        && StringUtils.isNotBlank(servedMetalake)
-        && !servedMetalake.equals(metalake)) {
-      // The Iceberg REST server serves exactly one metalake. Routing a 
different metalake's
-      // catalogs at it would 404 on every request, so report it as 
unavailable instead.
-      LOG.debug(
-          "Iceberg REST service serves metalake {}, not the requested metalake 
{}; not "
-              + "reporting its endpoint for auto-discovery.",
-          servedMetalake,
-          metalake);
-      return null;
-    }
-
-    String advertisedUri = 
StringUtils.trimToNull(config.get(ADVERTISED_URI_KEY));
-    if (advertisedUri != null) {
-      return checkAdvertisedUri(advertisedUri);
-    }
-
-    String host = config.getOrDefault(HOST_KEY, DEFAULT_HOST);
-    if (isWildcardHost(host)) {
-      // The Iceberg REST server binds to all interfaces, so it has no single 
externally
-      // reachable address of its own. The caller already reached this 
Gravitino server at some
-      // resolvable host, so reuse it — this holds whenever both services 
share a host, which is
-      // the common case, and callers with a genuinely split topology can 
still set
-      // gravitino.iceberg.rest-uri manually.
-      host = getHttpRequest().getServerName();
-    }
-    boolean enableHttps = 
Boolean.parseBoolean(config.getOrDefault(ENABLE_HTTPS_KEY, "false"));
-    String scheme = enableHttps ? "https" : "http";
-    int port =
-        parsePort(
-            config,
-            enableHttps ? HTTPS_PORT_KEY : HTTP_PORT_KEY,
-            enableHttps ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT);
-    return String.format("%s://%s:%d/iceberg", scheme, bracketIfIPv6(host), 
port);
-  }
-
-  private static String checkAdvertisedUri(String value) {
-    boolean valid;
-    try {
-      URI uri = new URI(value);
-      // URI accepts any non-negative integer as a port; -1 means no explicit 
port.
-      int port = uri.getPort();
-      valid =
-          StringUtils.equalsAnyIgnoreCase(uri.getScheme(), "http", "https")
-              && StringUtils.isNotBlank(uri.getHost())
-              && (port == -1 || (port >= 1 && port <= 65535))
-              && uri.getQuery() == null
-              && uri.getFragment() == null;
-    } catch (URISyntaxException e) {
-      valid = false;
-    }
-    if (!valid) {
-      throw new IllegalStateException(
-          String.format(
-              "Invalid Iceberg REST service %s '%s': expected an absolute 
http(s) URI with a "
-                  + "host, a port in 1-65535 if present, and no query or 
fragment",
-              ADVERTISED_URI_KEY, value));
-    }
-    return value;
-  }
-
-  // An IPv6 literal host (e.g. "::1", from an explicit config value or from
-  // HttpServletRequest#getServerName()) must be bracketed to form a valid URI 
authority;
-  // otherwise its colons are parsed as the port separator. A hostname or IPv4 
address never
-  // contains a colon, so this only ever fires for IPv6.
-  private static String bracketIfIPv6(String host) {
-    if (host.contains(":") && !host.startsWith("[")) {
-      return "[" + host + "]";
-    }
-    return host;
-  }
-
-  private static int parsePort(Map<String, String> config, String key, int 
defaultPort) {
-    String value = config.getOrDefault(key, "");
-    if (StringUtils.isBlank(value)) {
-      return defaultPort;
-    }
-    try {
-      return Integer.parseInt(value.trim());
-    } catch (NumberFormatException e) {
-      return defaultPort;
-    }
-  }
-
-  private static boolean isWildcardHost(String host) {
-    return StringUtils.isBlank(host)
-        || "0.0.0.0".equals(host)
-        || "::".equals(host)
-        || "[::]".equals(host)
-        || "0:0:0:0:0:0:0:0".equals(host);
-  }
 }

Reply via email to