This is an automated email from the ASF dual-hosted git repository. jerryshao pushed a commit to branch 1.2.0-hotfix in repository https://gitbox.apache.org/repos/asf/gravitino.git
commit 36c3c46ce4a5b706f134687e604a0b62ef2c0a6c Author: Mark Hoerth <[email protected]> AuthorDate: Thu Apr 23 01:08:41 2026 -0700 [#10846] fix(server): Add /health.html alias for legacy GTM standards (#10847) Follow-up to #10839. #10840 added /health, /health/live, /health/ready root-level aliases but dropped /health.html during the merge refactor. This restores /health.html and maps it to the canonical /api/health aggregate endpoint — legacy GTM onboarding standards commonly hardcode the .html extension as a well-known probe path. Adds test coverage for /health.html in TestHealthAliasServlet. Fixes #10846 Restores `/health.html` as a root-level alias forwarding to `/api/health`. Three small changes: 1. `HealthAliasServlet.doGet` — map `/health.html` to `/api/health` (the aggregate endpoint), rather than the default `/api` prefix rule which would incorrectly target `/api/health.html`. 2. `GravitinoServer` — register the alias servlet at `/health.html` (outside the `/health/*` glob spec Jetty uses for sub-paths). 3. `TestHealthAliasServlet` — extend the parameterized forward test to cover `/health.html → /api/health`. `/health/ready` for "enterprise GTM standards that hardcode well-known root paths." `/health.html` — arguably the most commonly hardcoded GTM probe path (predates MicroProfile Health, common in enterprise GTM onboarding specs) — was part of the original PR and was dropped during the merge refactor. Without `.html`, customers whose GTM standards specifically require `/health.html` cannot use the aliases. Smoke test confirms the fix: curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8090/health # 200 curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8090/health.html # 200 (was 404) curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8090/api/health # 200 Yes — adds one public endpoint (`GET /health.html`). Returns the same aggregate JSON body as `/health` and `/api/health`. No other endpoint behavior is changed. - `TestHealthAliasServlet` parameterized test extended with `/health.html → /api/health` case. - Manual verification against a running Gravitino instance: `/health.html` returns 200 with the same body as `/api/health` (134-byte aggregate JSON); existing `/health`, `/health/live`, `/health/ready`, `/api/health*`, and `/api/version` all unchanged. - **Special-case mapping for `.html`**: `HealthAliasServlet` maps `/health.html` to the aggregate `/api/health`, not `/api/health.html`. `.html` is a legacy web-server convention used as a sibling of `/health`, not a sub-path. Keeping the mapping rule explicit in the servlet rather than adding a separate alias endpoint keeps the servlet single-purpose. - **Test gap observation**: this regression slipped through #10840 because `TestHealthAliasServlet` mocks `getRequestDispatcher()`, which validates the servlet's path-rewriting logic but doesn't exercise Jetty's path-spec matching in `GravitinoServer.addServlet(...)`. The bug was one level up from what the unit test covers. Worth considering an integration test that boots the server and curls each GTM-relevant path — happy to open a separate issue if useful. - **This change should be cherry-picked to `branch-1.2`** alongside the main #10840 backport, so RBC and other 1.2 customers get the complete fix in one coherent change. --------- Co-authored-by: Mark Hoerth <[email protected]> Co-authored-by: Jerry Shao <[email protected]> Co-authored-by: Claude Sonnet 4.6 <[email protected]> --- docs/open-api/health.yaml | 6 ++++++ .../main/java/org/apache/gravitino/server/ServerConfig.java | 2 +- .../gravitino/server/authentication/AuthenticationFilter.java | 6 +++--- .../server/authentication/TestAuthenticationFilter.java | 1 + .../java/org/apache/gravitino/server/GravitinoServer.java | 6 ++++-- .../org/apache/gravitino/server/web/HealthAliasServlet.java | 11 +++++++---- .../apache/gravitino/server/web/TestHealthAliasServlet.java | 1 + 7 files changed, 23 insertions(+), 10 deletions(-) diff --git a/docs/open-api/health.yaml b/docs/open-api/health.yaml index d6956446bd..dd4be8bdae 100644 --- a/docs/open-api/health.yaml +++ b/docs/open-api/health.yaml @@ -29,6 +29,8 @@ paths: Returns aggregate health status combining liveness and readiness checks. Returns 200 when all checks pass; 503 when any check fails. This endpoint is exempt from authentication so probes can reach it without credentials. + Root-level aliases at /health and /health.html (outside the /api base path) are also + available for GTMs that require probes at well-known root paths. security: [] responses: "200": @@ -50,6 +52,8 @@ paths: Returns 200 as long as the HTTP server thread can respond. Use this for Kubernetes liveness probes to determine whether to restart a pod. This endpoint is exempt from authentication so probes can reach it without credentials. + A root-level alias at /health/live (outside the /api base path) is also available + for GTMs that require probes at well-known root paths. security: [] responses: "200": @@ -70,6 +74,8 @@ paths: Returns 503 when the entity store is unavailable or slow. Use this for Kubernetes readiness probes to control traffic routing. This endpoint is exempt from authentication so probes can reach it without credentials. + A root-level alias at /health/ready (outside the /api base path) is also available + for GTMs that require probes at well-known root paths. security: [] responses: "200": diff --git a/server-common/src/main/java/org/apache/gravitino/server/ServerConfig.java b/server-common/src/main/java/org/apache/gravitino/server/ServerConfig.java index 532f334c76..71177a8edb 100644 --- a/server-common/src/main/java/org/apache/gravitino/server/ServerConfig.java +++ b/server-common/src/main/java/org/apache/gravitino/server/ServerConfig.java @@ -39,7 +39,7 @@ public class ServerConfig extends Config { new ConfigBuilder("gravitino.server.health.entityStore.probeTimeoutMs") .doc( "Timeout in milliseconds for the entity-store readiness probe used by /api/health/ready") - .version(ConfigConstants.VERSION_1_3_0) + .version(ConfigConstants.VERSION_1_2_0) .longConf() .checkValue(value -> value > 0, ConfigConstants.POSITIVE_NUMBER_ERROR_MSG) .createWithDefault(2000L); diff --git a/server-common/src/main/java/org/apache/gravitino/server/authentication/AuthenticationFilter.java b/server-common/src/main/java/org/apache/gravitino/server/authentication/AuthenticationFilter.java index 4919f7963b..37bb5af9b4 100644 --- a/server-common/src/main/java/org/apache/gravitino/server/authentication/AuthenticationFilter.java +++ b/server-common/src/main/java/org/apache/gravitino/server/authentication/AuthenticationFilter.java @@ -140,15 +140,15 @@ public class AuthenticationFilter implements Filter { if (path == null) { return false; } - // Also match /health and /health/* — the root-level aliases that forward to /api/health/*. - // During a forward, getRequestURI() returns the original URI, not the forwarded target. + // Also match /health, /health/*, and /health.html — root-level aliases that forward to + // /api/health/*. During a forward, getRequestURI() returns the original URI, not the target. return path.equals("/health") || path.startsWith("/health/") + || path.equals("/health.html") || path.equals("/api/health") || path.startsWith("/api/health/"); } - @Override public void destroy() {} } diff --git a/server-common/src/test/java/org/apache/gravitino/server/authentication/TestAuthenticationFilter.java b/server-common/src/test/java/org/apache/gravitino/server/authentication/TestAuthenticationFilter.java index 7366b89db6..8c227727b4 100644 --- a/server-common/src/test/java/org/apache/gravitino/server/authentication/TestAuthenticationFilter.java +++ b/server-common/src/test/java/org/apache/gravitino/server/authentication/TestAuthenticationFilter.java @@ -132,6 +132,7 @@ public class TestAuthenticationFilter { "/health", "/health/live", "/health/ready", + "/health.html", "/api/health", "/api/health/", "/api/health/live", diff --git a/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java b/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java index 6f86755bea..9df59d3ced 100644 --- a/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java +++ b/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java @@ -174,9 +174,11 @@ public class GravitinoServer extends ResourceConfig { Servlet configServlet = new ConfigServlet(serverConfig); server.addServlet(configServlet, "/configs"); - // Root-level alias for enterprise GTMs that require probes at well-known root paths. - // Forwards /health, /health/live, /health/ready to the canonical /api/health/* endpoints. + // Root-level aliases for enterprise GTMs that require probes at well-known root paths. + // Forwards /health, /health/live, /health/ready, and /health.html to the canonical + // /api/health/* endpoints. server.addServlet(new HealthAliasServlet(), "/health/*"); + server.addServlet(new HealthAliasServlet(), "/health.html"); server.addCustomFilters(API_ANY_PATH); server.addFilter(new VersioningFilter(), API_ANY_PATH); diff --git a/server/src/main/java/org/apache/gravitino/server/web/HealthAliasServlet.java b/server/src/main/java/org/apache/gravitino/server/web/HealthAliasServlet.java index daf21b9057..532bc44e4a 100644 --- a/server/src/main/java/org/apache/gravitino/server/web/HealthAliasServlet.java +++ b/server/src/main/java/org/apache/gravitino/server/web/HealthAliasServlet.java @@ -26,8 +26,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** - * Serves root-level health paths ({@code /health}, {@code /health/live}, {@code /health/ready}) by - * forwarding to the canonical {@code /api/health/*} endpoints. + * Serves root-level health paths ({@code /health}, {@code /health/live}, {@code /health/ready}, + * {@code /health.html}) by forwarding to the canonical {@code /api/health/*} endpoints. * * <p>This alias exists for compatibility with enterprise global traffic managers that require * probes at well-known root paths. The canonical implementation remains at {@code /api/health}. @@ -37,8 +37,11 @@ public class HealthAliasServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - // Prepend /api to the incoming path: /health → /api/health, /health/live → /api/health/live - String targetPath = "/api" + req.getRequestURI(); + // Map root-level health paths to their canonical /api/health counterparts. + // /health and /health.html both target the aggregate /api/health; legacy GTM + // standards sometimes hardcode the .html extension. + String uri = req.getRequestURI(); + String targetPath = "/health.html".equals(uri) ? "/api/health" : "/api" + uri; RequestDispatcher dispatcher = req.getRequestDispatcher(targetPath); if (dispatcher == null) { resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "health dispatcher unavailable"); diff --git a/server/src/test/java/org/apache/gravitino/server/web/TestHealthAliasServlet.java b/server/src/test/java/org/apache/gravitino/server/web/TestHealthAliasServlet.java index 876af0111b..1d0e6e3e8a 100644 --- a/server/src/test/java/org/apache/gravitino/server/web/TestHealthAliasServlet.java +++ b/server/src/test/java/org/apache/gravitino/server/web/TestHealthAliasServlet.java @@ -34,6 +34,7 @@ public class TestHealthAliasServlet { @ParameterizedTest @CsvSource({ "/health, /api/health", + "/health.html, /api/health", "/health/live, /api/health/live", "/health/ready, /api/health/ready" })
