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/ResteasyServerCommonProcessor.java b/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java
index ed472ecaf1eea..cf6a1ffebddd1 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.
@@ -138,6 +140,7 @@ static final class ResteasyConfig {
* annotated application classes.
*/
@ConfigItem(defaultValue = "/")
+ @ConvertWith(NormalizeRootHttpPathConverter.class)
String path;
/**
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/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..11f0690f7b6f0 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 resolvePath("foo")} will return {@literal /foo}
+ * - {@code resolvePath("/foo")} will return {@literal /foo}
+ *
+ * Given {@literal quarkus.http.root-path=/app}
+ *
+ * - {@code resolvePath("foo")} will return {@literal /app/foo}
+ * - {@code resolvePath("/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/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 {