From 4a66f79481214a5129e63d3dd2794a0bc7f46f20 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 19 Aug 2021 11:52:51 +0200 Subject: [PATCH] Normalize the quarkus.http.root-path and RESTEasy deployment path at config level Make sure both paths always start and end with a '/' so that we don't have any risk of having logic working with '/test' and not with '/test/'. Adjust the servlet context path too. Also simplify a few things thanks to that. Fixes #19492 --- .../deployment/util/UriNormalizationUtil.java | 8 ++- .../NormalizeRootHttpPathConverter.java | 40 ++++++++++++++ .../ResteasyDeploymentBuildItem.java | 4 +- .../ResteasyServerCommonProcessor.java | 12 ++++- .../ResteasyServerConfigBuildItem.java | 2 +- .../deployment/ResteasyServletProcessor.java | 5 +- .../ResteasyStandaloneBuildStep.java | 27 +++------- .../undertow/deployment/ServletConfig.java | 54 +++++++++++++++++-- .../deployment/UndertowBuildStep.java | 10 ++-- .../runtime/UndertowDeploymentRecorder.java | 13 +---- .../deployment/HttpRootPathBuildItem.java | 27 ++++++++++ .../http/deployment/VertxHttpProcessor.java | 4 +- .../deployment/HttpRootPathBuildItemTest.java | 2 +- .../http/runtime/HttpBuildTimeConfig.java | 5 +- .../http/runtime/StaticResourcesRecorder.java | 4 +- 15 files changed, 161 insertions(+), 56 deletions(-) create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/configuration/NormalizeRootHttpPathConverter.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/UriNormalizationUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/util/UriNormalizationUtil.java index 3a629bf968b16..77ef04d84404f 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/util/UriNormalizationUtil.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/util/UriNormalizationUtil.java @@ -91,14 +91,18 @@ public static URI toURI(String path, boolean trailingSlash) { * * * @param base URI to resolve relative paths. Use {@link #toURI(String, boolean)} to construct this parameter. - * + * * @param segment Relative or absolute path * @param trailingSlash true if resulting URI must end with a '/' * @throws IllegalArgumentException if the path contains invalid characters or path segments. */ public static URI normalizeWithBase(URI base, String segment, boolean trailingSlash) { if (segment == null || segment.trim().isEmpty()) { - return base; + if ("/".equals(base.getPath())) { + return base; + } + // otherwise, make sure trailingSlash is honored + return toURI(base.getPath(), trailingSlash); } URI segmentUri = toURI(segment, trailingSlash); URI resolvedUri = base.resolve(segmentUri); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/NormalizeRootHttpPathConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/NormalizeRootHttpPathConverter.java new file mode 100644 index 0000000000000..fcda229d2ec0c --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/NormalizeRootHttpPathConverter.java @@ -0,0 +1,40 @@ +package io.quarkus.runtime.configuration; + +import static io.quarkus.runtime.configuration.ConverterSupport.DEFAULT_QUARKUS_CONVERTER_PRIORITY; + +import javax.annotation.Priority; + +import org.eclipse.microprofile.config.spi.Converter; + +/** + * A converter to normalize paths that are considered root of something. + *

+ * Any path coming out of this converter will have a leading and ending '/'. + *

+ * Do NOT use this converter for paths that could be relative. + */ +@Priority(DEFAULT_QUARKUS_CONVERTER_PRIORITY) +public class NormalizeRootHttpPathConverter implements Converter { + + private static final String SLASH = "/"; + + @Override + public String convert(String value) throws IllegalArgumentException, NullPointerException { + if (value == null) { + return SLASH; + } + + value = value.trim(); + if (SLASH.equals(value)) { + return value; + } + if (!value.startsWith(SLASH)) { + value = SLASH + value; + } + if (!value.endsWith(SLASH)) { + value = value + SLASH; + } + + return value; + } +} diff --git a/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyDeploymentBuildItem.java b/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyDeploymentBuildItem.java index ad88ad4e84596..86b7141d67d4c 100644 --- a/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyDeploymentBuildItem.java +++ b/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyDeploymentBuildItem.java @@ -5,12 +5,12 @@ import io.quarkus.builder.item.SimpleBuildItem; public final class ResteasyDeploymentBuildItem extends SimpleBuildItem { - private ResteasyDeployment deployment; private String rootPath; + private ResteasyDeployment deployment; public ResteasyDeploymentBuildItem(String rootPath, ResteasyDeployment deployment) { + this.rootPath = rootPath.startsWith("/") ? rootPath : "/" + rootPath; this.deployment = deployment; - this.rootPath = rootPath; } public ResteasyDeployment getDeployment() { diff --git a/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java b/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java index e6e19e00900e2..f8bc8f674c319 100755 --- a/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java +++ b/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java @@ -77,6 +77,8 @@ import io.quarkus.resteasy.server.common.spi.AllowedJaxRsAnnotationPrefixBuildItem; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigRoot; +import io.quarkus.runtime.annotations.ConvertWith; +import io.quarkus.runtime.configuration.NormalizeRootHttpPathConverter; /** * Processor that builds the RESTEasy server configuration. @@ -135,9 +137,17 @@ static final class ResteasyConfig { /** * Set this to override the default path for JAX-RS resources if there are no - * annotated application classes. + * annotated application classes. This path is specified with a leading {@literal /}, but is resolved relative + * to {@literal quarkus.http.root-path}. + *

*/ @ConfigItem(defaultValue = "/") + @ConvertWith(NormalizeRootHttpPathConverter.class) String path; /** diff --git a/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerConfigBuildItem.java b/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerConfigBuildItem.java index 7705fd62b6b53..a092917849ee6 100644 --- a/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerConfigBuildItem.java +++ b/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerConfigBuildItem.java @@ -21,7 +21,7 @@ public final class ResteasyServerConfigBuildItem extends SimpleBuildItem { */ public ResteasyServerConfigBuildItem(String rootPath, String path, Map initParameters) { this.rootPath = rootPath; - this.path = path; + this.path = path.startsWith("/") ? path : "/" + path; this.initParameters = initParameters; } diff --git a/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyServletProcessor.java b/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyServletProcessor.java index 8a2f3435ef55e..1bac198489765 100644 --- a/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyServletProcessor.java +++ b/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyServletProcessor.java @@ -53,9 +53,8 @@ public void jaxrsConfig( BuildProducer resteasyJaxrsConfig, HttpRootPathBuildItem httpRootPathBuildItem) { if (resteasyServerConfig.isPresent()) { - String rp = resteasyServerConfig.get().getRootPath(); - String rootPath = httpRootPathBuildItem.resolvePath(rp.startsWith("/") ? rp.substring(1) : rp); - String defaultPath = httpRootPathBuildItem.resolvePath(resteasyServerConfig.get().getPath()); + String rootPath = httpRootPathBuildItem.relativePath(resteasyServerConfig.get().getRootPath()); + String defaultPath = resteasyServerConfig.get().getPath(); deprecatedResteasyJaxrsConfig.produce(new ResteasyJaxrsConfigBuildItem(defaultPath)); resteasyJaxrsConfig diff --git a/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyStandaloneBuildStep.java b/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyStandaloneBuildStep.java index 3fca6ae74703f..8561b96d185db 100644 --- a/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyStandaloneBuildStep.java +++ b/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyStandaloneBuildStep.java @@ -23,9 +23,9 @@ import io.quarkus.resteasy.server.common.deployment.ResteasyDeploymentBuildItem; import io.quarkus.vertx.core.deployment.CoreVertxBuildItem; import io.quarkus.vertx.http.deployment.DefaultRouteBuildItem; +import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; import io.quarkus.vertx.http.deployment.RequireVirtualHttpBuildItem; import io.quarkus.vertx.http.deployment.RouteBuildItem; -import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig; import io.quarkus.vertx.http.runtime.HttpConfiguration; import io.quarkus.vertx.http.runtime.VertxHttpRecorder; import io.vertx.core.Handler; @@ -50,32 +50,17 @@ public void staticInit(ResteasyStandaloneRecorder recorder, ResteasyDeploymentBuildItem deployment, ApplicationArchivesBuildItem applicationArchivesBuildItem, ResteasyInjectionReadyBuildItem resteasyInjectionReady, - HttpBuildTimeConfig httpConfig, + HttpRootPathBuildItem httpRootPathBuildItem, BuildProducer standalone) throws Exception { if (capabilities.isPresent(Capability.SERVLET)) { return; } - String deploymentRootPath = null; - // The context path + the resources path - String rootPath = httpConfig.rootPath; - if (deployment != null) { - deploymentRootPath = deployment.getRootPath(); - if (rootPath.endsWith("/")) { - if (deploymentRootPath.startsWith("/")) { - rootPath += deploymentRootPath.substring(1); - } else { - rootPath += deploymentRootPath; - } - } else if (!deploymentRootPath.equals("/")) { - if (!deploymentRootPath.startsWith("/")) { - rootPath += "/"; - } - rootPath += deploymentRootPath; - } - recorder.staticInit(deployment.getDeployment(), rootPath); - standalone.produce(new ResteasyStandaloneBuildItem(deploymentRootPath)); + // the deployment path is always relative to the HTTP root path + recorder.staticInit(deployment.getDeployment(), + httpRootPathBuildItem.relativePath(deployment.getRootPath())); + standalone.produce(new ResteasyStandaloneBuildItem(deployment.getRootPath())); } } diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletConfig.java b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletConfig.java index b14400aaff5a7..a028318c79bda 100644 --- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletConfig.java +++ b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletConfig.java @@ -1,22 +1,37 @@ package io.quarkus.undertow.deployment; +import static io.quarkus.runtime.configuration.ConverterSupport.DEFAULT_QUARKUS_CONVERTER_PRIORITY; + import java.util.Optional; +import javax.annotation.Priority; + +import org.eclipse.microprofile.config.spi.Converter; + import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; +import io.quarkus.runtime.annotations.ConvertWith; @ConfigRoot(phase = ConfigPhase.BUILD_TIME) public class ServletConfig { /** - * The context path to serve all Servlet context from. This will also affect any resources - * that run as a Servlet, e.g. JAX-RS. - * - * Note that this is relative to the HTTP root path set in quarkus.http.root-path, so if the context path - * is /bar and the http root is /foo then the actual Servlet path will be /foo/bar. + * The context path for Servlet content. This will determine the path used to + * resolve all Servlet-based resources, including JAX-RS resources - when using the Undertow extension in conjunction with + * RESTEasy. + *

+ * This path is specified with a leading {@literal /}, but is resolved relative + * to {@literal quarkus.http.root-path}. + *

    + *
  • If {@literal quarkus.http.root-path=/} and {@code quarkus.servlet.context-path=/bar}, the servlet path will be + * {@literal /bar}
  • + *
  • If {@literal quarkus.http.root-path=/foo} and {@code quarkus.servlet.context-path=/bar}, the servlet path will be + * {@literal /foo/bar}
  • + *
*/ @ConfigItem + @ConvertWith(ContextPathConverter.class) Optional contextPath; /** @@ -25,4 +40,33 @@ public class ServletConfig { @ConfigItem(defaultValue = "UTF-8") public String defaultCharset; + /** + * This converter adds a '/' at the beginning of the context path but does not add one at the end, given we want to support + * binding to a context without an ending '/'. + *

+ * See ContextPathTestCase for an example. + */ + @Priority(DEFAULT_QUARKUS_CONVERTER_PRIORITY) + public static class ContextPathConverter implements Converter { + + private static final String SLASH = "/"; + + @Override + public String convert(String value) throws IllegalArgumentException, NullPointerException { + if (value == null) { + return SLASH; + } + + value = value.trim(); + if (SLASH.equals(value)) { + return value; + } + if (!value.startsWith(SLASH)) { + value = SLASH + value; + } + + return value; + } + } + } diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java index 633596dc8b88a..fa8f71c8f185f 100644 --- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java +++ b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java @@ -116,6 +116,7 @@ import io.quarkus.undertow.runtime.UndertowDeploymentRecorder; import io.quarkus.undertow.runtime.UndertowHandlersConfServletExtension; import io.quarkus.vertx.http.deployment.DefaultRouteBuildItem; +import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; import io.quarkus.vertx.http.deployment.RouteBuildItem; import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig; import io.quarkus.vertx.http.runtime.HttpConfiguration; @@ -319,11 +320,7 @@ public ServletContextPathBuildItem contextPath( WebMetadataBuildItem webMetadataBuildItem) { String contextPath; if (servletConfig.contextPath.isPresent()) { - if (!servletConfig.contextPath.get().startsWith("/")) { - contextPath = "/" + servletConfig.contextPath; - } else { - contextPath = servletConfig.contextPath.get(); - } + contextPath = servletConfig.contextPath.get(); } else if (webMetadataBuildItem.getWebMetaData().getDefaultContextPath() != null) { contextPath = webMetadataBuildItem.getWebMetaData().getDefaultContextPath(); } else { @@ -351,6 +348,7 @@ public ServletDeploymentManagerBuildItem build(List servlets, ShutdownContextBuildItem shutdownContext, KnownPathsBuildItem knownPaths, HttpBuildTimeConfig httpBuildTimeConfig, + HttpRootPathBuildItem httpRootPath, ServletConfig servletConfig) throws Exception { ObjectSubstitutionBuildItem.Holder holder = new ObjectSubstitutionBuildItem.Holder(ServletSecurityInfo.class, @@ -365,7 +363,7 @@ public ServletDeploymentManagerBuildItem build(List servlets, String contextPath = servletContextPathBuildItem.getServletContextPath(); RuntimeValue deployment = recorder.createDeployment("test", knownPaths.knownFiles, knownPaths.knownDirectories, - launchMode.getLaunchMode(), shutdownContext, contextPath, httpBuildTimeConfig.rootPath, + launchMode.getLaunchMode(), shutdownContext, httpRootPath.relativePath(contextPath), servletConfig.defaultCharset, webMetaData.getRequestCharacterEncoding(), webMetaData.getResponseCharacterEncoding(), httpBuildTimeConfig.auth.proactive, webMetaData.getWelcomeFileList() != null ? webMetaData.getWelcomeFileList().getWelcomeFiles() : null); diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java index aa6532a2f53f3..730b858eea093 100644 --- a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java +++ b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java @@ -156,18 +156,9 @@ public static void setHotDeploymentResources(List resources) { } public RuntimeValue createDeployment(String name, Set knownFile, Set knownDirectories, - LaunchMode launchMode, ShutdownContext context, String contextPath, String httpRootPath, String defaultCharset, + LaunchMode launchMode, ShutdownContext context, String mountPoint, String defaultCharset, String requestCharacterEncoding, String responseCharacterEncoding, boolean proactiveAuth, List welcomeFiles) { - String realMountPoint; - if (contextPath.equals("/")) { - realMountPoint = httpRootPath; - } else if (httpRootPath.equals("/")) { - realMountPoint = contextPath; - } else { - realMountPoint = httpRootPath + contextPath; - } - DeploymentInfo d = new DeploymentInfo(); d.setDefaultRequestEncoding(requestCharacterEncoding); d.setDefaultResponseEncoding(responseCharacterEncoding); @@ -175,7 +166,7 @@ public RuntimeValue createDeployment(String name, Set kn d.setSessionIdGenerator(new QuarkusSessionIdGenerator()); d.setClassLoader(getClass().getClassLoader()); d.setDeploymentName(name); - d.setContextPath(realMountPoint); + d.setContextPath(mountPoint); d.setEagerFilterInit(true); ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl == null) { diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpRootPathBuildItem.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpRootPathBuildItem.java index dbdc17f8fb5ec..40afedb5e3c4c 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpRootPathBuildItem.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpRootPathBuildItem.java @@ -66,6 +66,33 @@ public String resolvePath(String path) { return UriNormalizationUtil.normalizeWithBase(rootPath, path, false).getPath(); } + /** + * Resolve path that is always relative into an absolute path. + * Whether the path is relative or absolute, it will be resolved against `quarkus.http.root-path`, + * by removing the '/' in the latter case. + *

+ * Given {@literal quarkus.http.root-path=/} + *

    + *
  • {@code relativePath("foo")} will return {@literal /foo}
  • + *
  • {@code relativePath("/foo")} will return {@literal /foo}
  • + *
+ * Given {@literal quarkus.http.root-path=/app} + *
    + *
  • {@code relativePath("foo")} will return {@literal /app/foo}
  • + *
  • {@code relativePath("/foo")} will return {@literal /app/foo}
  • + *
+ *

+ * The returned path will not end with a slash. + * + * @param path Path to be resolved to an absolute path. + * @return An absolute path not ending with a slash + * @see UriNormalizationUtil#normalizeWithBase(URI, String, boolean) + */ + public String relativePath(String path) { + String relativePath = path.startsWith("/") ? path.substring(1) : path; + return UriNormalizationUtil.normalizeWithBase(rootPath, relativePath, false).getPath(); + } + public HttpRootPathBuildItem.Builder routeBuilder() { return new HttpRootPathBuildItem.Builder(this); } diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java index f2b3ce5928e8a..a1ef76ed0c7c7 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java @@ -198,6 +198,7 @@ ServiceStartBuildItem finalizeRouter( LaunchModeBuildItem launchMode, List defaultRoutes, List filters, VertxWebRouterBuildItem httpRouteRouter, + HttpRootPathBuildItem httpRootPathBuildItem, NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, HttpBuildTimeConfig httpBuildTimeConfig, HttpConfiguration httpConfiguration, List requireBodyHandlerBuildItems, @@ -256,7 +257,8 @@ ServiceStartBuildItem finalizeRouter( recorder.finalizeRouter(beanContainer.getValue(), defaultRoute.map(DefaultRouteBuildItem::getRoute).orElse(null), - listOfFilters, vertx.getVertx(), lrc, mainRouter, httpRouteRouter.getHttpRouter(), httpBuildTimeConfig.rootPath, + listOfFilters, vertx.getVertx(), lrc, mainRouter, httpRouteRouter.getHttpRouter(), + httpRootPathBuildItem.getRootPath(), launchMode.getLaunchMode(), !requireBodyHandlerBuildItems.isEmpty(), bodyHandler, httpConfiguration, gracefulShutdownFilter, shutdownConfig, executorBuildItem.getExecutorProxy()); diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/deployment/HttpRootPathBuildItemTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/deployment/HttpRootPathBuildItemTest.java index 456675376bf6b..1e98718b35c87 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/deployment/HttpRootPathBuildItemTest.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/deployment/HttpRootPathBuildItemTest.java @@ -18,7 +18,7 @@ void testResolvePathWithSlash() { void testResolvePathWithSlashApp() { HttpRootPathBuildItem buildItem = new HttpRootPathBuildItem("/app"); - Assertions.assertEquals("/app/", buildItem.resolvePath("")); + Assertions.assertEquals("/app", buildItem.resolvePath("")); Assertions.assertEquals("/app/foo", buildItem.resolvePath("foo")); Assertions.assertEquals("/foo", buildItem.resolvePath("/foo")); } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java index a0a8383110a2a..dbae58949e924 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java @@ -5,6 +5,8 @@ import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; +import io.quarkus.runtime.annotations.ConvertWith; +import io.quarkus.runtime.configuration.NormalizeRootHttpPathConverter; import io.vertx.core.http.ClientAuth; @ConfigRoot(name = "http", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) @@ -14,6 +16,7 @@ public class HttpBuildTimeConfig { * The HTTP root path. All web content will be served relative to this root path. */ @ConfigItem(defaultValue = "/") + @ConvertWith(NormalizeRootHttpPathConverter.class) public String rootPath; public AuthConfig auth; @@ -43,7 +46,7 @@ public class HttpBuildTimeConfig { * Non-application endpoints will be served from the specified path. * * `${quarkus.http.root-path}` -> Setting this path to the same value as HTTP root path disables * this root path. All extension-provided endpoints will be served from `${quarkus.http.root-path}`. - * + * * @asciidoclet */ @ConfigItem(defaultValue = "q") diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/StaticResourcesRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/StaticResourcesRecorder.java index 9dbca79a5143c..5c9c0061e125e 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/StaticResourcesRecorder.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/StaticResourcesRecorder.java @@ -56,7 +56,9 @@ public Consumer start() { StaticHandler staticHandler = StaticHandler.create(META_INF_RESOURCES).setDefaultContentEncoding("UTF-8"); handlers.add(ctx -> { String rel = ctx.mountPoint() == null ? ctx.normalisedPath() - : ctx.normalisedPath().substring(ctx.mountPoint().length()); + : ctx.normalisedPath().substring( + // let's be extra careful here in case Vert.x normalizes the mount points at some point + ctx.mountPoint().endsWith("/") ? ctx.mountPoint().length() - 1 : ctx.mountPoint().length()); if (knownPaths.contains(rel)) { staticHandler.handle(ctx); } else {