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}.
+ *
+ * - If {@literal quarkus.http.root-path=/} and {@code quarkus.resteasy.path=/bar}, the JAX-RS resource path will be
+ * {@literal /bar}
+ * - If {@literal quarkus.http.root-path=/foo} and {@code quarkus.resteasy.path=/bar}, the JAX-RS resource path will
+ * be {@literal /foo/bar}
+ *
*/
@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 {