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
new file mode 100644
index 0000000000000..bb6b858232c5a
--- /dev/null
+++ b/core/deployment/src/main/java/io/quarkus/deployment/util/UriNormalizationUtil.java
@@ -0,0 +1,107 @@
+package io.quarkus.deployment.util;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+ * Common URI path resolution
+ */
+public class UriNormalizationUtil {
+ private UriNormalizationUtil() {
+ }
+
+ /**
+ * Create a URI path from a string. The specified path can not contain
+ * relative {@literal ..} segments or {@literal %} characters.
+ *
+ * Examples:
+ *
+ *
{@code toUri("/", true)} will return a URI with path {@literal /}
+ *
{@code toUri("/", false)} will return a URI with an empty path {@literal /}
+ *
{@code toUri("./", true)} will return a URI with path {@literal /}
+ *
{@code toUri("./", false)} will return a URI with an empty path {@literal /}
+ *
{@code toUri("foo/", true)} will return a URI with path {@literal foo/}
+ *
{@code toUri("foo/", false)} will return a URI with an empty path {@literal foo}
+ *
+ *
+ *
+ * @param path String to convert into a URI
+ * @param trailingSlash true if resulting URI must end with a '/'
+ * @throws IllegalArgumentException if the path contains invalid characters or path segments.
+ */
+ public static URI toURI(String path, boolean trailingSlash) {
+ try {
+ // replace inbound // with /
+ path = path.replaceAll("//", "/");
+ // remove trailing slash if result shouldn't have one
+ if (!trailingSlash && path.endsWith("/")) {
+ path = path.substring(0, path.length() - 1);
+ }
+
+ if (path.contains("..") || path.contains("%")) {
+ throw new IllegalArgumentException("Specified path can not contain '..' or '%'. Path was " + path);
+ }
+ URI uri = new URI(path).normalize();
+ if (uri.getPath().equals("")) {
+ return trailingSlash ? new URI("/") : new URI("");
+ } else if (trailingSlash && !path.endsWith("/")) {
+ uri = new URI(uri.getPath() + "/");
+ }
+ return uri;
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Specified path is an invalid URI. Path was " + path, e);
+ }
+ }
+
+ /**
+ * Resolve a string path against a URI base. The specified path can not contain
+ * relative {@literal ..} segments or {@literal %} characters.
+ *
+ * Relative paths will be resolved against the specified base URI.
+ * Absolute paths will be normalized and returned.
+ *
+ * Examples:
+ *
+ *
{@code normalizeWithBase(new URI("/"), "example", true)}
+ * will return a URI with path {@literal /example/}
+ *
{@code normalizeWithBase(new URI("/"), "example", false)}
+ * will return a URI with an empty path {@literal /example}
+ *
{@code normalizeWithBase(new URI("/"), "/example", true)}
+ * will return a URI with path {@literal /example/}
+ *
{@code normalizeWithBase(new URI("/"), "/example", false)}
+ * will return a URI with an empty {@literal /example
+ *
+ *
{@code normalizeWithBase(new URI("/prefix/"), "example", true)}
+ * will return a URI with path {@literal /prefix/example/}
+ *
{@code normalizeWithBase(new URI("/prefix/"), "example", false)}
+ * will return a URI with an empty path {@literal /prefix/example}
+ *
{@code normalizeWithBase(new URI("/prefix/"), "/example", true)}
+ * will return a URI with path {@literal /example/}
+ *
{@code normalizeWithBase(new URI("/prefix/"), "/example", false)}
+ * will return a URI with an empty path {@literal /example}
+ *
+ *
{@code normalizeWithBase(new URI("foo/"), "example", true)}
+ * will return a URI with path {@literal foo/example/}
+ *
{@code normalizeWithBase(new URI("foo/"), "example", false)}
+ * will return a URI with an empty path {@literal foo/example}
+ *
{@code normalizeWithBase(new URI("foo/"), "/example", true)}
+ * will return a URI with path {@literal /example/}
+ *
{@code normalizeWithBase(new URI("foo/"), "/example", false)}
+ * will return a URI with an empty path {@literal /example}
+ *
+ *
+ * @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;
+ }
+ URI segmentUri = toURI(segment, trailingSlash);
+ URI resolvedUri = base.resolve(segmentUri);
+ return resolvedUri;
+ }
+}
diff --git a/core/deployment/src/test/java/io/quarkus/deployment/util/UriNormalizationTest.java b/core/deployment/src/test/java/io/quarkus/deployment/util/UriNormalizationTest.java
new file mode 100644
index 0000000000000..6cc52d80e2c4e
--- /dev/null
+++ b/core/deployment/src/test/java/io/quarkus/deployment/util/UriNormalizationTest.java
@@ -0,0 +1,62 @@
+package io.quarkus.deployment.util;
+
+import java.net.URI;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class UriNormalizationTest {
+ @Test
+ void testToUri() {
+ Assertions.assertEquals("/", UriNormalizationUtil.toURI("/", true).getPath());
+ Assertions.assertEquals("", UriNormalizationUtil.toURI("/", false).getPath());
+
+ Assertions.assertEquals("/", UriNormalizationUtil.toURI("./", true).getPath());
+ Assertions.assertEquals("", UriNormalizationUtil.toURI("./", false).getPath());
+
+ Assertions.assertEquals("bob/", UriNormalizationUtil.toURI("bob/", true).getPath());
+ Assertions.assertEquals("bob", UriNormalizationUtil.toURI("bob/", false).getPath());
+ }
+
+ @Test
+ void testExamples() {
+ URI root = UriNormalizationUtil.toURI("/", true);
+ URI prefix = UriNormalizationUtil.toURI("/prefix", true);
+ URI foo = UriNormalizationUtil.toURI("foo", true);
+
+ Assertions.assertEquals("/example/", UriNormalizationUtil.normalizeWithBase(root, "example", true).getPath());
+ Assertions.assertEquals("/example", UriNormalizationUtil.normalizeWithBase(root, "example", false).getPath());
+ Assertions.assertEquals("/example/", UriNormalizationUtil.normalizeWithBase(root, "/example", true).getPath());
+ Assertions.assertEquals("/example", UriNormalizationUtil.normalizeWithBase(root, "/example", false).getPath());
+
+ Assertions.assertEquals("/prefix/example/", UriNormalizationUtil.normalizeWithBase(prefix, "example", true).getPath());
+ Assertions.assertEquals("/prefix/example", UriNormalizationUtil.normalizeWithBase(prefix, "example", false).getPath());
+ Assertions.assertEquals("/example/", UriNormalizationUtil.normalizeWithBase(prefix, "/example", true).getPath());
+ Assertions.assertEquals("/example", UriNormalizationUtil.normalizeWithBase(prefix, "/example", false).getPath());
+
+ Assertions.assertEquals("foo/example/", UriNormalizationUtil.normalizeWithBase(foo, "example", true).getPath());
+ Assertions.assertEquals("foo/example", UriNormalizationUtil.normalizeWithBase(foo, "example", false).getPath());
+ Assertions.assertEquals("/example/", UriNormalizationUtil.normalizeWithBase(foo, "/example", true).getPath());
+ Assertions.assertEquals("/example", UriNormalizationUtil.normalizeWithBase(foo, "/example", false).getPath());
+ }
+
+ @Test
+ void testDubiousUriPaths() {
+ URI root = UriNormalizationUtil.toURI("/", true);
+
+ Assertions.assertEquals("/", UriNormalizationUtil.normalizeWithBase(root, "#example", false).getPath());
+ Assertions.assertEquals("/", UriNormalizationUtil.normalizeWithBase(root, "?example", false).getPath());
+
+ Assertions.assertEquals("/example", UriNormalizationUtil.normalizeWithBase(root, "./example", false).getPath());
+ Assertions.assertEquals("/example", UriNormalizationUtil.normalizeWithBase(root, "//example", false).getPath());
+
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> UriNormalizationUtil.normalizeWithBase(root, "/%2fexample", false));
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> UriNormalizationUtil.normalizeWithBase(root, "junk/../example", false));
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> UriNormalizationUtil.normalizeWithBase(root, "junk/../../example", false));
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> UriNormalizationUtil.normalizeWithBase(root, "../example", false));
+ }
+}
diff --git a/docs/src/main/asciidoc/openapi-swaggerui.adoc b/docs/src/main/asciidoc/openapi-swaggerui.adoc
index 3e80e4f830608..8af97f105de1c 100644
--- a/docs/src/main/asciidoc/openapi-swaggerui.adoc
+++ b/docs/src/main/asciidoc/openapi-swaggerui.adoc
@@ -467,12 +467,13 @@ You can update the `/swagger-ui` sub path by setting the `quarkus.swagger-ui.pat
[source, properties]
----
-quarkus.swagger-ui.path=/my-custom-path
+quarkus.swagger-ui.path=my-custom-path
----
[WARNING]
====
The value `/` is not allowed as it blocks the application from serving anything else.
+A value prefixed with '/' makes it absolute and not relative.
====
Now, we are ready to run our application:
diff --git a/docs/src/main/asciidoc/writing-extensions.adoc b/docs/src/main/asciidoc/writing-extensions.adoc
index 98cbf49b487a7..97e774a399f98 100644
--- a/docs/src/main/asciidoc/writing-extensions.adoc
+++ b/docs/src/main/asciidoc/writing-extensions.adoc
@@ -1453,9 +1453,57 @@ As a CDI based runtime, Quarkus extensions often make CDI beans available as par
However, Quarkus DI solution does not support CDI Portable Extensions.
Instead, Quarkus extensions can make use of various link:cdi-reference[Build Time Extension Points].
-=== Quarkus DEV Console
+=== Quarkus DEV UI
-You can make your extension support the link:dev-console[Quarkus DEV Console] for a greater developer experience.
+You can make your extension support the link:dev-ui[Quarkus DEV UI] for a greater developer experience.
+
+=== Extension-defined endpoints
+
+Your extension can add additional, non-application endpoints to be served alongside endpoints
+for Health, Metrics, OpenAPI, Swagger UI, etc.
+
+Use a `NonApplicationRootPathBuildItem` to define an endpoint:
+
+[source%nowrap,java]
+----
+@BuildStep
+RouteBuildItem myExtensionRoute(NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) {
+ return nonApplicationRootPathBuildItem.routeBuilder()
+ .route("custom-endpoint")
+ .handler(new MyCustomHandler())
+ .displayOnNotFoundPage()
+ .build();
+}
+----
+
+Note that the path above does not start with a '/', indicating it is a relative path. The above
+endpoint will be served relative to the configured non-application endpoint root. The non-application
+endpoint root is `/q` by default, which means the resulting endpoint will be found at `/q/custom-endpoint`.
+
+Absolute paths are handled differently. If the above called `route("/custom-endpoint")`, the resulting
+endpoint will be found at `/custom-endpoint`.
+
+If an extension needs nested non-application endpoints:
+
+[source%nowrap,java]
+----
+@BuildStep
+RouteBuildItem myNestedExtensionRoute(NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) {
+ return nonApplicationRootPathBuildItem.routeBuilder()
+ .nestedRoute("custom-endpoint", "deep")
+ .handler(new MyCustomHandler())
+ .displayOnNotFoundPage()
+ .build();
+}
+----
+
+Given a default non-application endpoint root of `/q`, this will create an endpoint at `/q/custom-endpoint/deep`.
+
+Absolute paths also have an impact on nested endpoints. If the above called `nestedRoute("custom-endpoint", "/deep")`,
+the resulting endpoint will be found at `/deep`.
+
+Refer to the link:all-config#quarkus-vertx-http_quarkus.http.non-application-root-path[Quarkus Vertx HTTP configuration reference]
+for details on how the non-application root path is configured.
=== Extension Health Check
diff --git a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/MicrometerProcessor.java b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/MicrometerProcessor.java
index b4faaa601bb76..aeb2e8fae7c49 100644
--- a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/MicrometerProcessor.java
+++ b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/MicrometerProcessor.java
@@ -122,7 +122,7 @@ MetricsCapabilityBuildItem metricsCapabilityBuildItem() {
MetricsCapabilityBuildItem metricsCapabilityPrometheusBuildItem(
NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) {
return new MetricsCapabilityBuildItem(MetricsFactory.MICROMETER::equals,
- nonApplicationRootPathBuildItem.adjustPath(mConfig.export.prometheus.path));
+ nonApplicationRootPathBuildItem.resolvePath(mConfig.export.prometheus.path));
}
@BuildStep(onlyIf = MicrometerEnabled.class)
diff --git a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/export/JsonRegistryProcessor.java b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/export/JsonRegistryProcessor.java
index dbfd208e41966..9f4da05b43992 100644
--- a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/export/JsonRegistryProcessor.java
+++ b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/export/JsonRegistryProcessor.java
@@ -14,6 +14,7 @@
import io.quarkus.micrometer.runtime.export.JsonMeterRegistryProvider;
import io.quarkus.micrometer.runtime.export.JsonRecorder;
import io.quarkus.micrometer.runtime.registry.json.JsonMeterRegistry;
+import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
import io.quarkus.vertx.http.deployment.RouteBuildItem;
public class JsonRegistryProcessor {
@@ -34,17 +35,21 @@ public void initializeJsonRegistry(MicrometerConfig config,
BuildProducer registryProviders,
BuildProducer routes,
BuildProducer additionalBeans,
+ NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
JsonRecorder recorder) {
additionalBeans.produce(AdditionalBeanBuildItem.builder()
.addBeanClass(JsonMeterRegistryProvider.class)
.setUnremovable().build());
registryProviders.produce(new MicrometerRegistryProviderBuildItem(JsonMeterRegistry.class));
- routes.produce(new RouteBuildItem.Builder()
- .routeFunction(recorder.route(config.export.json.path))
+
+ routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
+ .routeFunction(config.export.json.path, recorder.route())
.handler(recorder.getHandler())
- .nonApplicationRoute(true)
+ .requiresLegacyRedirect()
.build());
- log.debug("Initialized a JSON meter registry on path=" + config.export.json.path);
+
+ log.debug("Initialized a JSON meter registry on path="
+ + nonApplicationRootPathBuildItem.resolvePath(config.export.json.path));
}
}
diff --git a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/export/PrometheusRegistryProcessor.java b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/export/PrometheusRegistryProcessor.java
index 33ee7261b7386..7c512dd094d44 100644
--- a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/export/PrometheusRegistryProcessor.java
+++ b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/export/PrometheusRegistryProcessor.java
@@ -15,6 +15,7 @@
import io.quarkus.micrometer.runtime.config.PrometheusConfigGroup;
import io.quarkus.micrometer.runtime.export.PrometheusMeterRegistryProvider;
import io.quarkus.micrometer.runtime.export.PrometheusRecorder;
+import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
import io.quarkus.vertx.http.deployment.RouteBuildItem;
/**
@@ -53,24 +54,25 @@ MicrometerRegistryProviderBuildItem createPrometheusRegistry(
@Record(value = ExecutionTime.STATIC_INIT)
void createPrometheusRoute(BuildProducer routes,
MicrometerConfig mConfig,
+ NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
PrometheusRecorder recorder) {
PrometheusConfigGroup pConfig = mConfig.export.prometheus;
log.debug("PROMETHEUS CONFIG: " + pConfig);
// Exact match for resources matched to the root path
- routes.produce(new RouteBuildItem.Builder()
- .routeFunction(recorder.route(pConfig.path))
+ routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
+ .routeFunction(pConfig.path, recorder.route())
.handler(recorder.getHandler())
- .nonApplicationRoute()
+ .requiresLegacyRedirect()
+ .displayOnNotFoundPage("Metrics", pConfig.path)
.build());
// Match paths that begin with the deployment path
- String matchPath = pConfig.path + (pConfig.path.endsWith("/") ? "*" : "/*");
- routes.produce(new RouteBuildItem.Builder()
- .routeFunction(recorder.route(matchPath))
+ routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
+ .routeFunction(pConfig.path + (pConfig.path.endsWith("/") ? "*" : "/*"), recorder.route())
.handler(recorder.getHandler())
- .nonApplicationRoute()
+ .requiresLegacyRedirect()
.build());
}
}
diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/JsonRegistryEnabledTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/JsonRegistryEnabledTest.java
new file mode 100644
index 0000000000000..3c2d574940bbc
--- /dev/null
+++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/JsonRegistryEnabledTest.java
@@ -0,0 +1,57 @@
+package io.quarkus.micrometer.deployment.export;
+
+import java.util.Set;
+
+import javax.inject.Inject;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
+import io.quarkus.micrometer.runtime.registry.json.JsonMeterRegistry;
+import io.quarkus.test.QuarkusUnitTest;
+import io.restassured.RestAssured;
+
+public class JsonRegistryEnabledTest {
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .withConfigurationResource("test-logging.properties")
+ .overrideConfigKey("quarkus.http.root-path", "/app")
+ .overrideConfigKey("quarkus.http.non-application-root-path", "relative")
+ .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false")
+ .overrideConfigKey("quarkus.micrometer.export.json.enabled", "true")
+ .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false")
+ .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
+ .addClass(PrometheusRegistryProcessor.REGISTRY_CLASS));
+
+ @Inject
+ MeterRegistry registry;
+
+ @Inject
+ JsonMeterRegistry jsonMeterRegistry;
+
+ @Test
+ public void testMeterRegistryPresent() {
+ // Prometheus is enabled (only registry)
+ Assertions.assertNotNull(registry, "A registry should be configured");
+ Set subRegistries = ((CompositeMeterRegistry) registry).getRegistries();
+ JsonMeterRegistry subPromRegistry = (JsonMeterRegistry) subRegistries.iterator().next();
+ Assertions.assertEquals(JsonMeterRegistry.class, subPromRegistry.getClass(), "Should be JsonMeterRegistry");
+ Assertions.assertEquals(subPromRegistry, jsonMeterRegistry,
+ "The only MeterRegistry should be the same bean as the JsonMeterRegistry");
+ }
+
+ @Test
+ public void metricsEndpoint() {
+ // RestAssured prepends /app for us
+ RestAssured.given()
+ .contentType("application/json")
+ .get("/relative/metrics")
+ .then()
+ .statusCode(200);
+ }
+}
diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/JsonConfigGroup.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/JsonConfigGroup.java
index f243dfe8c3c20..446ff3da2e25e 100644
--- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/JsonConfigGroup.java
+++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/JsonConfigGroup.java
@@ -16,9 +16,9 @@ public class JsonConfigGroup implements MicrometerConfig.CapabilityEnabled {
/**
* The path for the JSON metrics endpoint.
- * The default value is {@code /metrics}.
+ * The default value is {@code metrics}.
*/
- @ConfigItem(defaultValue = "/metrics")
+ @ConfigItem(defaultValue = "metrics")
public String path;
/**
diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/PrometheusConfigGroup.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/PrometheusConfigGroup.java
index 321b92066e1de..c8fccd1b65b8c 100644
--- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/PrometheusConfigGroup.java
+++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/PrometheusConfigGroup.java
@@ -20,9 +20,9 @@ public class PrometheusConfigGroup implements MicrometerConfig.CapabilityEnabled
/**
* The path for the prometheus metrics endpoint (produces text/plain).
- * The default value is {@code /metrics}.
+ * The default value is {@code metrics}.
*/
- @ConfigItem(defaultValue = "/metrics")
+ @ConfigItem(defaultValue = "metrics")
public String path;
@Override
diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/export/JsonRecorder.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/export/JsonRecorder.java
index f58b99b51e1d8..9ab0a33fb7d90 100644
--- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/export/JsonRecorder.java
+++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/export/JsonRecorder.java
@@ -1,11 +1,10 @@
package io.quarkus.micrometer.runtime.export;
-import java.util.function.Function;
+import java.util.function.Consumer;
import io.quarkus.micrometer.runtime.export.handlers.JsonHandler;
import io.quarkus.runtime.annotations.Recorder;
import io.vertx.ext.web.Route;
-import io.vertx.ext.web.Router;
@Recorder
public class JsonRecorder {
@@ -19,11 +18,11 @@ public JsonHandler getHandler() {
return handler;
}
- public Function route(String path) {
- return new Function() {
+ public Consumer route() {
+ return new Consumer() {
@Override
- public Route apply(Router router) {
- return router.route(path).order(2).produces("application/json");
+ public void accept(Route route) {
+ route.order(2).produces("application/json");
}
};
}
diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/export/PrometheusRecorder.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/export/PrometheusRecorder.java
index f2feea32521d9..998d3c2af4aab 100644
--- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/export/PrometheusRecorder.java
+++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/export/PrometheusRecorder.java
@@ -1,11 +1,10 @@
package io.quarkus.micrometer.runtime.export;
-import java.util.function.Function;
+import java.util.function.Consumer;
import io.quarkus.micrometer.runtime.export.handlers.PrometheusHandler;
import io.quarkus.runtime.annotations.Recorder;
import io.vertx.ext.web.Route;
-import io.vertx.ext.web.Router;
@Recorder
public class PrometheusRecorder {
@@ -19,11 +18,11 @@ public PrometheusHandler getHandler() {
return handler;
}
- public Function route(String path) {
- return new Function() {
+ public Consumer route() {
+ return new Consumer() {
@Override
- public Route apply(Router router) {
- return router.route(path).order(1).produces("text/plain");
+ public void accept(Route route) {
+ route.order(1).produces("text/plain");
}
};
}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveDevModeProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveDevModeProcessor.java
index 73fe8fae78fa3..62e710115e15c 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveDevModeProcessor.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveDevModeProcessor.java
@@ -62,7 +62,7 @@ void addAdditionalEndpointsExceptionMapper(List endpoints = displayableEndpoints
.stream()
- .map(v -> new AdditionalRouteDescription(v.getEndpoint(), v.getDescription()))
+ .map(v -> new AdditionalRouteDescription(v.getEndpoint(httpRoot), v.getDescription()))
.sorted()
.collect(Collectors.toList());
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/NotFoundExceptionMapper.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/NotFoundExceptionMapper.java
index a19e9c7b54dde..f0953b12a33fc 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/NotFoundExceptionMapper.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/NotFoundExceptionMapper.java
@@ -173,7 +173,7 @@ private Response respond(List descriptions, HttpHeaders hea
if (!additionalEndpoints.isEmpty()) {
sb.resourcesStart("Additional endpoints");
for (AdditionalRouteDescription additionalEndpoint : additionalEndpoints) {
- sb.staticResourcePath(adjustRoot(httpRoot, additionalEndpoint.getUri()),
+ sb.staticResourcePath(additionalEndpoint.getUri(),
additionalEndpoint.getDescription());
}
sb.resourcesEnd();
diff --git a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyBuiltinsProcessor.java b/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyBuiltinsProcessor.java
index 7caa2245b78ff..0de72fccfedde 100644
--- a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyBuiltinsProcessor.java
+++ b/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyBuiltinsProcessor.java
@@ -126,7 +126,7 @@ void addAdditionalEndpointsExceptionMapper(List endpoints = displayableEndpoints
.stream()
.map(displayableAdditionalBuildItem -> new AdditionalRouteDescription(
- displayableAdditionalBuildItem.getEndpoint(), displayableAdditionalBuildItem.getDescription()))
+ displayableAdditionalBuildItem.getEndpoint(httpRoot), displayableAdditionalBuildItem.getDescription()))
.sorted()
.collect(Collectors.toList());
diff --git a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyServletProcessor.java b/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyServletProcessor.java
index bfce28a9bc7a4..0508e4e6ffe69 100644
--- a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyServletProcessor.java
+++ b/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyServletProcessor.java
@@ -56,8 +56,9 @@ public void jaxrsConfig(
BuildProducer resteasyJaxrsConfig,
HttpRootPathBuildItem httpRootPathBuildItem) {
if (resteasyServerConfig.isPresent()) {
- String rootPath = httpRootPathBuildItem.adjustPath(resteasyServerConfig.get().getRootPath());
- String defaultPath = httpRootPathBuildItem.adjustPath(resteasyServerConfig.get().getPath());
+ String rp = resteasyServerConfig.get().getRootPath();
+ String rootPath = httpRootPathBuildItem.resolvePath(rp.startsWith("/") ? rp.substring(1) : rp);
+ String defaultPath = httpRootPathBuildItem.resolvePath(resteasyServerConfig.get().getPath());
deprecatedResteasyJaxrsConfig.produce(new ResteasyJaxrsConfigBuildItem(defaultPath));
resteasyJaxrsConfig
diff --git a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/NotFoundExceptionMapper.java b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/NotFoundExceptionMapper.java
index a5ff1c04db5b7..d8553592e98ba 100644
--- a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/NotFoundExceptionMapper.java
+++ b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/NotFoundExceptionMapper.java
@@ -309,7 +309,7 @@ private Response respond(List descriptions) {
if (!additionalEndpoints.isEmpty()) {
sb.resourcesStart("Additional endpoints");
for (AdditionalRouteDescription additionalEndpoint : additionalEndpoints) {
- sb.staticResourcePath(adjustRoot(httpRoot, additionalEndpoint.getUri()),
+ sb.staticResourcePath(additionalEndpoint.getUri(),
additionalEndpoint.getDescription());
}
sb.resourcesEnd();
diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java
index 4fa39309e35a9..50a616dc55d9b 100644
--- a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java
+++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java
@@ -26,6 +26,7 @@
import io.quarkus.deployment.Feature;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.annotations.Consume;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
@@ -189,6 +190,7 @@ void buildSchemaEndpoint(
routeProducer.produce(new RouteBuildItem.Builder()
.route(graphQLConfig.rootPath + SCHEMA_PATH)
.handler(schemaHandler)
+ .displayOnNotFoundPage("MicroProfile GraphQL Schema")
.blockingRoute()
.build());
@@ -196,17 +198,15 @@ void buildSchemaEndpoint(
@Record(ExecutionTime.RUNTIME_INIT)
@BuildStep
+ @Consume(BeanContainerBuildItem.class)
void buildExecutionEndpoint(
BuildProducer routeProducer,
- BuildProducer notFoundPageDisplayableEndpointProducer,
SmallRyeGraphQLInitializedBuildItem graphQLInitializedBuildItem,
SmallRyeGraphQLRecorder recorder,
ShutdownContextBuildItem shutdownContext,
LaunchModeBuildItem launchMode,
BodyHandlerBuildItem bodyHandlerBuildItem,
- SmallRyeGraphQLConfig graphQLConfig,
- BeanContainerBuildItem beanContainerBuildItem // don't remove this - makes sure beanContainer is initialized
- ) {
+ SmallRyeGraphQLConfig graphQLConfig) {
/*
* Ugly Hack
@@ -220,23 +220,15 @@ void buildExecutionEndpoint(
if (launchMode.getLaunchMode() == LaunchMode.DEVELOPMENT) {
recorder.setupClDevMode(shutdownContext);
}
- // add graphql endpoint for not found display in dev or test mode
- if (launchMode.getLaunchMode().isDevOrTest()) {
- notFoundPageDisplayableEndpointProducer
- .produce(new NotFoundPageDisplayableEndpointBuildItem(graphQLConfig.rootPath,
- "MicroProfile GraphQL Endpoint"));
- notFoundPageDisplayableEndpointProducer
- .produce(new NotFoundPageDisplayableEndpointBuildItem(graphQLConfig.rootPath + SCHEMA_PATH,
- "MicroProfile GraphQL Schema"));
- }
Boolean allowGet = ConfigProvider.getConfig().getOptionalValue(ConfigKey.ALLOW_GET, boolean.class).orElse(false);
Handler executionHandler = recorder.executionHandler(graphQLInitializedBuildItem.getInitialized(),
allowGet);
routeProducer.produce(new RouteBuildItem.Builder()
- .routeFunction(recorder.routeFunction(graphQLConfig.rootPath, bodyHandlerBuildItem.getHandler()))
+ .routeFunction(graphQLConfig.rootPath, recorder.routeFunction(bodyHandlerBuildItem.getHandler()))
.handler(executionHandler)
+ .displayOnNotFoundPage("MicroProfile GraphQL Endpoint")
.blockingRoute()
.build());
@@ -469,9 +461,8 @@ void getGraphqlUiFinalDestination(
"quarkus.smallrye-graphql.root-path-ui was set to \"/\", this is not allowed as it blocks the application from serving anything else.");
}
- String graphQLPath = httpRootPath.adjustPath(graphQLConfig.rootPath);
- String graphQLUiPath = httpRootPath
- .adjustPath(nonApplicationRootPathBuildItem.adjustPath(graphQLConfig.ui.rootPath));
+ String graphQLPath = httpRootPath.resolvePath(graphQLConfig.rootPath);
+ String graphQLUiPath = nonApplicationRootPathBuildItem.resolvePath(graphQLConfig.ui.rootPath);
AppArtifact artifact = WebJarUtil.getAppArtifact(curateOutcomeBuildItem, GRAPHQL_UI_WEBJAR_GROUP_ID,
GRAPHQL_UI_WEBJAR_ARTIFACT_ID);
@@ -484,10 +475,6 @@ void getGraphqlUiFinalDestination(
smallRyeGraphQLBuildProducer
.produce(new SmallRyeGraphQLBuildItem(tempPath.toAbsolutePath().toString(), graphQLUiPath));
- notFoundPageDisplayableEndpointProducer
- .produce(new NotFoundPageDisplayableEndpointBuildItem(
- nonApplicationRootPathBuildItem.adjustPath(graphQLConfig.ui.rootPath) + "/",
- "MicroProfile GraphQL UI"));
// Handle live reload of branding files
if (liveReloadBuildItem.isLiveReload() && !liveReloadBuildItem.getChangedResources().isEmpty()) {
@@ -532,20 +519,22 @@ void registerGraphQLUiHandler(
SmallRyeGraphQLRuntimeConfig runtimeConfig,
SmallRyeGraphQLBuildItem smallRyeGraphQLBuildItem,
LaunchModeBuildItem launchMode,
+ NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
SmallRyeGraphQLConfig graphQLConfig) throws Exception {
if (shouldInclude(launchMode, graphQLConfig)) {
Handler handler = recorder.uiHandler(smallRyeGraphQLBuildItem.getGraphqlUiFinalDestination(),
smallRyeGraphQLBuildItem.getGraphqlUiPath(), runtimeConfig);
- routeProducer.produce(new RouteBuildItem.Builder()
- .nonApplicationRoute(true)
+ routeProducer.produce(nonApplicationRootPathBuildItem.routeBuilder()
.route(graphQLConfig.ui.rootPath)
+ .displayOnNotFoundPage("MicroProfile GraphQL UI")
.handler(handler)
+ .requiresLegacyRedirect()
.build());
- routeProducer.produce(new RouteBuildItem.Builder()
- .nonApplicationRoute(true)
+ routeProducer.produce(nonApplicationRootPathBuildItem.routeBuilder()
.route(graphQLConfig.ui.rootPath + "/*")
.handler(handler)
+ .requiresLegacyRedirect()
.build());
}
}
diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLUIConfig.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLUIConfig.java
index 9c426cffbecbf..19cee4b8415a6 100644
--- a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLUIConfig.java
+++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLUIConfig.java
@@ -10,7 +10,7 @@ public class SmallRyeGraphQLUIConfig {
* The path where GraphQL UI is available.
* The value `/` is not allowed as it blocks the application from serving anything else.
*/
- @ConfigItem(defaultValue = "/graphql-ui")
+ @ConfigItem(defaultValue = "graphql-ui")
String rootPath;
/**
diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java
index c2ca6c4ed1742..4ed8144b50219 100644
--- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java
+++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java
@@ -1,6 +1,6 @@
package io.quarkus.smallrye.graphql.runtime;
-import java.util.function.Function;
+import java.util.function.Consumer;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.spi.CDI;
@@ -18,7 +18,6 @@
import io.smallrye.graphql.schema.model.Schema;
import io.vertx.core.Handler;
import io.vertx.ext.web.Route;
-import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
@Recorder
@@ -76,11 +75,11 @@ public void run() {
});
}
- public Function routeFunction(String rootPath, Handler bodyHandler) {
- return new Function() {
+ public Consumer routeFunction(Handler bodyHandler) {
+ return new Consumer() {
@Override
- public Route apply(Router router) {
- return router.route(rootPath).handler(bodyHandler);
+ public void accept(Route route) {
+ route.handler(bodyHandler);
}
};
}
diff --git a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthConfig.java b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthConfig.java
index b7cba3263c84f..9a80dcc1bed0a 100644
--- a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthConfig.java
+++ b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthConfig.java
@@ -10,31 +10,31 @@ public class SmallRyeHealthConfig {
/**
* Root path for health-checking endpoints.
*/
- @ConfigItem(defaultValue = "/health")
+ @ConfigItem(defaultValue = "health")
String rootPath;
/**
* The relative path of the liveness health-checking endpoint.
*/
- @ConfigItem(defaultValue = "/live")
+ @ConfigItem(defaultValue = "live")
String livenessPath;
/**
* The relative path of the readiness health-checking endpoint.
*/
- @ConfigItem(defaultValue = "/ready")
+ @ConfigItem(defaultValue = "ready")
String readinessPath;
/**
* The relative path of the health group endpoint.
*/
- @ConfigItem(defaultValue = "/group")
+ @ConfigItem(defaultValue = "group")
String groupPath;
/**
* The relative path of the wellness health-checking endpoint.
*/
- @ConfigItem(defaultValue = "/well")
+ @ConfigItem(defaultValue = "well")
String wellnessPath;
/**
diff --git a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java
index 99e57df6b6ed1..571a7a321c72b 100644
--- a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java
+++ b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java
@@ -159,21 +159,6 @@ void build(SmallRyeHealthRecorder recorder,
feature.produce(new FeatureBuildItem(Feature.SMALLRYE_HEALTH));
- // add health endpoints to not found page
- if (launchMode.getLaunchMode().isDevOrTest()) {
- String basePath = nonApplicationRootPathBuildItem.adjustPath(healthConfig.rootPath);
- displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(basePath));
- String subcontextBasePath = adjustSubcontextBasePath(basePath);
- displayableEndpoints
- .produce(new NotFoundPageDisplayableEndpointBuildItem(subcontextBasePath + healthConfig.livenessPath));
- displayableEndpoints
- .produce(new NotFoundPageDisplayableEndpointBuildItem(subcontextBasePath + healthConfig.readinessPath));
- displayableEndpoints
- .produce(new NotFoundPageDisplayableEndpointBuildItem(subcontextBasePath + healthConfig.groupPath));
- displayableEndpoints
- .produce(new NotFoundPageDisplayableEndpointBuildItem(subcontextBasePath + healthConfig.wellnessPath));
- }
-
// Discover the beans annotated with @Health, @Liveness, @Readiness, @HealthGroup,
// @HealthGroups and @Wellness even if no scope is defined
beanDefiningAnnotation.produce(new BeanDefiningAnnotationBuildItem(HEALTH));
@@ -209,6 +194,7 @@ void build(SmallRyeHealthRecorder recorder,
@BuildStep
public void defineHealthRoutes(BuildProducer routes,
BeanArchiveIndexBuildItem beanArchiveIndex,
+ NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
SmallRyeHealthConfig healthConfig) {
IndexView index = beanArchiveIndex.getIndex();
@@ -219,29 +205,30 @@ public void defineHealthRoutes(BuildProducer routes,
warnIfJaxRsPathUsed(index, WELLNESS);
// Register the health handler
- routes.produce(new RouteBuildItem.Builder()
+ routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
.route(healthConfig.rootPath)
.handler(new SmallRyeHealthHandler())
+ .requiresLegacyRedirect()
+ .displayOnNotFoundPage()
.blockingRoute()
- .nonApplicationRoute()
.build());
- String subcontextBasePath = adjustSubcontextBasePath(healthConfig.rootPath);
-
// Register the liveness handler
- routes.produce(new RouteBuildItem.Builder()
- .route(subcontextBasePath + healthConfig.livenessPath)
+ routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
+ .nestedRoute(healthConfig.rootPath, healthConfig.livenessPath)
.handler(new SmallRyeLivenessHandler())
+ .requiresLegacyRedirect()
+ .displayOnNotFoundPage()
.blockingRoute()
- .nonApplicationRoute()
.build());
// Register the readiness handler
- routes.produce(new RouteBuildItem.Builder()
- .route(subcontextBasePath + healthConfig.readinessPath)
+ routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
+ .nestedRoute(healthConfig.rootPath, healthConfig.readinessPath)
.handler(new SmallRyeReadinessHandler())
+ .requiresLegacyRedirect()
+ .displayOnNotFoundPage()
.blockingRoute()
- .nonApplicationRoute()
.build());
// Find all health groups
@@ -258,29 +245,32 @@ public void defineHealthRoutes(BuildProducer routes,
}
// Register the health group handlers
- routes.produce(new RouteBuildItem.Builder()
- .route(subcontextBasePath + healthConfig.groupPath)
+ routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
+ .nestedRoute(healthConfig.rootPath, healthConfig.groupPath)
.handler(new SmallRyeHealthGroupHandler())
+ .requiresLegacyRedirect()
+ .displayOnNotFoundPage()
.blockingRoute()
- .nonApplicationRoute()
.build());
SmallRyeIndividualHealthGroupHandler handler = new SmallRyeIndividualHealthGroupHandler();
for (String healthGroup : healthGroups) {
- routes.produce(new RouteBuildItem.Builder()
- .route(subcontextBasePath + healthConfig.groupPath + "/" + healthGroup)
+ routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
+ .nestedRoute(healthConfig.rootPath, healthConfig.groupPath + "/" + healthGroup)
.handler(handler)
+ .requiresLegacyRedirect()
+ .displayOnNotFoundPage()
.blockingRoute()
- .nonApplicationRoute()
.build());
}
// Register the wellness handler
- routes.produce(new RouteBuildItem.Builder()
- .route(subcontextBasePath + healthConfig.wellnessPath)
+ routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
+ .nestedRoute(healthConfig.rootPath, healthConfig.wellnessPath)
.handler(new SmallRyeWellnessHandler())
+ .requiresLegacyRedirect()
+ .displayOnNotFoundPage()
.blockingRoute()
- .nonApplicationRoute()
.build());
}
@@ -294,11 +284,10 @@ public void includeInOpenAPIEndpoint(BuildProducer livenessPathItemProducer,
BuildProducer readinessPathItemProducer) {
- String subcontextBasePath = adjustSubcontextBasePath(healthConfig.rootPath);
-
livenessPathItemProducer.produce(
new KubernetesHealthLivenessPathBuildItem(
- httpConfig
- .adjustPath(frameworkRootPath.adjustPath(subcontextBasePath + healthConfig.livenessPath))));
+ frameworkRootPath.resolveNestedPath(healthConfig.rootPath, healthConfig.livenessPath)));
readinessPathItemProducer.produce(
new KubernetesHealthReadinessPathBuildItem(
- httpConfig
- .adjustPath(frameworkRootPath.adjustPath(subcontextBasePath + healthConfig.readinessPath))));
+ frameworkRootPath.resolveNestedPath(healthConfig.rootPath, healthConfig.readinessPath)));
}
@BuildStep
@@ -400,7 +385,6 @@ public void transform(TransformationContext ctx) {
void registerUiExtension(
BuildProducer generatedResourceProducer,
BuildProducer nativeImageResourceProducer,
- BuildProducer notFoundPageDisplayableEndpointProducer,
BuildProducer smallRyeHealthBuildProducer,
HttpRootPathBuildItem httpRootPath,
NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
@@ -416,8 +400,9 @@ void registerUiExtension(
"quarkus.smallrye-health.root-path-ui was set to \"/\", this is not allowed as it blocks the application from serving anything else.");
}
- String healthPath = httpRootPath.adjustPath(nonApplicationRootPathBuildItem.adjustPath(healthConfig.rootPath));
- String healthUiPath = httpRootPath.adjustPath(nonApplicationRootPathBuildItem.adjustPath(healthConfig.ui.rootPath));
+ String healthPath = httpRootPath.resolvePath(nonApplicationRootPathBuildItem.resolvePath(healthConfig.rootPath));
+ String healthUiPath = httpRootPath
+ .resolvePath(nonApplicationRootPathBuildItem.resolvePath(healthConfig.ui.rootPath));
AppArtifact artifact = WebJarUtil.getAppArtifact(curateOutcomeBuildItem, HEALTH_UI_WEBJAR_GROUP_ID,
HEALTH_UI_WEBJAR_ARTIFACT_ID);
@@ -430,11 +415,6 @@ void registerUiExtension(
smallRyeHealthBuildProducer
.produce(new SmallRyeHealthBuildItem(tempPath.toAbsolutePath().toString(), healthUiPath));
- notFoundPageDisplayableEndpointProducer
- .produce(new NotFoundPageDisplayableEndpointBuildItem(
- nonApplicationRootPathBuildItem
- .adjustPath(healthConfig.ui.rootPath + "/")));
-
// Handle live reload of branding files
if (liveReloadBuildItem.isLiveReload() && !liveReloadBuildItem.getChangedResources().isEmpty()) {
WebJarUtil.hotReloadBrandingChanges(curateOutcomeBuildItem, launchMode, artifact,
@@ -470,21 +450,23 @@ void registerHealthUiHandler(
SmallRyeHealthRecorder recorder,
SmallRyeHealthRuntimeConfig runtimeConfig,
SmallRyeHealthBuildItem smallRyeHealthBuildItem,
+ NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
LaunchModeBuildItem launchMode,
SmallRyeHealthConfig healthConfig) throws Exception {
if (shouldInclude(launchMode, healthConfig)) {
Handler handler = recorder.uiHandler(smallRyeHealthBuildItem.getHealthUiFinalDestination(),
smallRyeHealthBuildItem.getHealthUiPath(), runtimeConfig);
- routeProducer.produce(new RouteBuildItem.Builder()
+ routeProducer.produce(nonApplicationRootPathBuildItem.routeBuilder()
.route(healthConfig.ui.rootPath)
+ .displayOnNotFoundPage("Health UI")
+ .requiresLegacyRedirect()
.handler(handler)
- .nonApplicationRoute()
.build());
- routeProducer.produce(new RouteBuildItem.Builder()
+ routeProducer.produce(nonApplicationRootPathBuildItem.routeBuilder()
.route(healthConfig.ui.rootPath + "/*")
+ .requiresLegacyRedirect()
.handler(handler)
- .nonApplicationRoute()
.build());
}
}
@@ -504,8 +486,4 @@ public String updateApiUrl(String original, String healthPath) {
private static boolean shouldInclude(LaunchModeBuildItem launchMode, SmallRyeHealthConfig healthConfig) {
return launchMode.getLaunchMode().isDevOrTest() || healthConfig.ui.alwaysInclude;
}
-
- private String adjustSubcontextBasePath(String basePath) {
- return basePath.equals("/") ? "" : basePath;
- }
}
diff --git a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthUIConfig.java b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthUIConfig.java
index 04a04c45ac421..f85e119bbce3c 100644
--- a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthUIConfig.java
+++ b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthUIConfig.java
@@ -9,7 +9,7 @@ public class SmallRyeHealthUIConfig {
* The path where Health UI is available.
* The value `/` is not allowed as it blocks the application from serving anything else.
*/
- @ConfigItem(defaultValue = "/health-ui")
+ @ConfigItem(defaultValue = "health-ui")
String rootPath;
/**
diff --git a/extensions/smallrye-health/deployment/src/test/java/io/quarkus/smallrye/health/test/ui/CustomConfigTest.java b/extensions/smallrye-health/deployment/src/test/java/io/quarkus/smallrye/health/test/ui/CustomConfigTest.java
index 105b68b0eace6..4068bb4e3adba 100644
--- a/extensions/smallrye-health/deployment/src/test/java/io/quarkus/smallrye/health/test/ui/CustomConfigTest.java
+++ b/extensions/smallrye-health/deployment/src/test/java/io/quarkus/smallrye/health/test/ui/CustomConfigTest.java
@@ -20,7 +20,7 @@ public class CustomConfigTest {
@Test
public void shouldUseCustomConfig() {
- RestAssured.when().get("/q/custom").then().statusCode(200).body(containsString("SmallRye Health"));
- RestAssured.when().get("/q/custom/index.html").then().statusCode(200).body(containsString("SmallRye Health"));
+ RestAssured.when().get("/custom").then().statusCode(200).body(containsString("SmallRye Health"));
+ RestAssured.when().get("/custom/index.html").then().statusCode(200).body(containsString("SmallRye Health"));
}
}
diff --git a/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java b/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java
index 2173745fc5279..5bfbadd59f33d 100644
--- a/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java
+++ b/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java
@@ -23,7 +23,6 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.function.Function;
import java.util.function.Predicate;
import javax.enterprise.context.Dependent;
@@ -92,8 +91,6 @@
import io.smallrye.metrics.interceptors.MetricsInterceptor;
import io.smallrye.metrics.interceptors.SimplyTimedInterceptor;
import io.smallrye.metrics.interceptors.TimedInterceptor;
-import io.vertx.ext.web.Route;
-import io.vertx.ext.web.Router;
public class SmallRyeMetricsProcessor {
static final Logger LOGGER = Logger.getLogger("io.quarkus.smallrye.metrics.deployment.SmallRyeMetricsProcessor");
@@ -104,7 +101,7 @@ static final class SmallRyeMetricsConfig {
/**
* The path to the metrics handler.
*/
- @ConfigItem(defaultValue = "/metrics")
+ @ConfigItem(defaultValue = "metrics")
String path;
/**
@@ -124,7 +121,7 @@ static final class SmallRyeMetricsConfig {
/**
* Whether or not detailed JAX-RS metrics should be enabled.
- *
+ *
* See MicroProfile
* Metrics: Optional REST metrics.
@@ -144,7 +141,7 @@ MetricsConfigurationBuildItem metricsConfigurationBuildItem() {
MetricsCapabilityBuildItem metricsCapabilityBuildItem(NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) {
if (metrics.extensionsEnabled) {
return new MetricsCapabilityBuildItem(MetricsFactory.MP_METRICS::equals,
- nonApplicationRootPathBuildItem.adjustPath(metrics.path));
+ nonApplicationRootPathBuildItem.resolvePath(metrics.path));
}
return null;
}
@@ -157,24 +154,23 @@ void createRoute(BuildProducer routes,
BuildProducer displayableEndpoints,
LaunchModeBuildItem launchModeBuildItem,
BeanContainerBuildItem beanContainer) {
- Function route = recorder.route(metrics.path + (metrics.path.endsWith("/") ? "*" : "/*"));
- Function slash = recorder.route(metrics.path);
// add metrics endpoint for not found display in dev or test mode
if (launchModeBuildItem.getLaunchMode().isDevOrTest()) {
displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(metrics.path));
}
- routes.produce(new RouteBuildItem.Builder()
- .routeFunction(route)
- .handler(recorder.handler(frameworkRoot.adjustPathIncludingHttpRootPath(metrics.path)))
+ routes.produce(frameworkRoot.routeBuilder()
+ .route(metrics.path + (metrics.path.endsWith("/") ? "*" : "/*"))
+ .handler(recorder.handler(frameworkRoot.resolvePath(metrics.path)))
+ .requiresLegacyRedirect()
+ .displayOnNotFoundPage("Metrics", metrics.path)
.blockingRoute()
- .nonApplicationRoute()
.build());
- routes.produce(new RouteBuildItem.Builder()
- .routeFunction(slash)
- .handler(recorder.handler(frameworkRoot.adjustPathIncludingHttpRootPath(metrics.path)))
+ routes.produce(frameworkRoot.routeBuilder()
+ .route(metrics.path)
+ .handler(recorder.handler(frameworkRoot.resolvePath(metrics.path)))
+ .requiresLegacyRedirect()
.blockingRoute()
- .nonApplicationRoute()
.build());
}
diff --git a/extensions/smallrye-metrics/deployment/src/test/java/io/quarkus/smallrye/metrics/deployment/DevModeMetricsTest.java b/extensions/smallrye-metrics/deployment/src/test/java/io/quarkus/smallrye/metrics/deployment/DevModeMetricsTest.java
index 79651f30a5772..5a9a0736694a7 100644
--- a/extensions/smallrye-metrics/deployment/src/test/java/io/quarkus/smallrye/metrics/deployment/DevModeMetricsTest.java
+++ b/extensions/smallrye-metrics/deployment/src/test/java/io/quarkus/smallrye/metrics/deployment/DevModeMetricsTest.java
@@ -96,6 +96,7 @@ public void test() {
// jax-rs metrics are enabled
when().get("/q/metrics").then()
+ .statusCode(200)
.body(containsString("io.quarkus.smallrye.metrics.deployment.DevModeMetricsTest$MetricsResource"));
// disable jax-rs metrics via quarkus.resteasy.metrics.enabled
diff --git a/extensions/smallrye-metrics/deployment/src/test/java/io/quarkus/smallrye/metrics/test/MetricsHandlerPathTest.java b/extensions/smallrye-metrics/deployment/src/test/java/io/quarkus/smallrye/metrics/test/MetricsHandlerPathTest.java
index 3255a3f8309dc..70be9b1bd0822 100644
--- a/extensions/smallrye-metrics/deployment/src/test/java/io/quarkus/smallrye/metrics/test/MetricsHandlerPathTest.java
+++ b/extensions/smallrye-metrics/deployment/src/test/java/io/quarkus/smallrye/metrics/test/MetricsHandlerPathTest.java
@@ -22,6 +22,7 @@ public class MetricsHandlerPathTest {
@Test
public void testMetricsEndpointAccessibility() {
+ RestAssured.basePath = "/";
RestAssured.when()
// no need to prepend the /custom here because it will be reflected in RestAssured.basePath
.get("/framework/metrics")
diff --git a/extensions/smallrye-metrics/runtime/src/main/java/io/quarkus/smallrye/metrics/runtime/SmallRyeMetricsHandler.java b/extensions/smallrye-metrics/runtime/src/main/java/io/quarkus/smallrye/metrics/runtime/SmallRyeMetricsHandler.java
index fdeb4df0d5b30..d9db296a4fadd 100644
--- a/extensions/smallrye-metrics/runtime/src/main/java/io/quarkus/smallrye/metrics/runtime/SmallRyeMetricsHandler.java
+++ b/extensions/smallrye-metrics/runtime/src/main/java/io/quarkus/smallrye/metrics/runtime/SmallRyeMetricsHandler.java
@@ -31,6 +31,8 @@ public void handle(RoutingContext routingContext) {
HttpServerResponse response = routingContext.response();
HttpServerRequest request = routingContext.request();
Stream acceptHeaders = request.headers().getAll("Accept").stream();
+ routingContext.currentRoute().getPath();
+ routingContext.mountPoint();
try {
internalHandler.handleRequest(request.path(), metricsPath, request.rawMethod(), acceptHeaders,
diff --git a/extensions/smallrye-metrics/runtime/src/main/java/io/quarkus/smallrye/metrics/runtime/SmallRyeMetricsRecorder.java b/extensions/smallrye-metrics/runtime/src/main/java/io/quarkus/smallrye/metrics/runtime/SmallRyeMetricsRecorder.java
index f39d3eadac858..bf5651bc52ec3 100644
--- a/extensions/smallrye-metrics/runtime/src/main/java/io/quarkus/smallrye/metrics/runtime/SmallRyeMetricsRecorder.java
+++ b/extensions/smallrye-metrics/runtime/src/main/java/io/quarkus/smallrye/metrics/runtime/SmallRyeMetricsRecorder.java
@@ -14,7 +14,6 @@
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
-import java.util.function.Function;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.CDI;
@@ -50,8 +49,6 @@
import io.smallrye.metrics.elementdesc.MemberInfo;
import io.smallrye.metrics.interceptors.MetricResolver;
import io.smallrye.metrics.setup.MetricsMetadata;
-import io.vertx.ext.web.Route;
-import io.vertx.ext.web.Router;
@Recorder
public class SmallRyeMetricsRecorder {
@@ -90,15 +87,6 @@ public class SmallRyeMetricsRecorder {
private static final SmallRyeMetricsFactory factory = new SmallRyeMetricsFactory();
- public Function route(String name) {
- return new Function() {
- @Override
- public Route apply(Router router) {
- return router.route(name);
- }
- };
- }
-
public SmallRyeMetricsHandler handler(String metricsPath) {
SmallRyeMetricsHandler handler = new SmallRyeMetricsHandler();
handler.setMetricsPath(metricsPath);
diff --git a/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java b/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java
index 5879bd62994f9..beb930cdacefd 100644
--- a/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java
+++ b/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java
@@ -11,7 +11,7 @@ public final class SmallRyeOpenApiConfig {
/**
* The path at which to register the OpenAPI Servlet.
*/
- @ConfigItem(defaultValue = "/openapi")
+ @ConfigItem(defaultValue = "openapi")
public String path;
/**
diff --git a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java
index 69a697fe1d61e..c4d12bd1551f0 100644
--- a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java
+++ b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java
@@ -167,16 +167,15 @@ RouteBuildItem handler(LaunchModeBuildItem launch,
*/
if (launch.getLaunchMode() == LaunchMode.DEVELOPMENT) {
recorder.setupClDevMode(shutdownContext);
- displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(
- nonApplicationRootPathBuildItem.adjustPath(openApiConfig.path), "Open API Schema document"));
}
Handler handler = recorder.handler(openApiRuntimeConfig);
- return new RouteBuildItem.Builder()
+ return nonApplicationRootPathBuildItem.routeBuilder()
.route(openApiConfig.path)
.handler(handler)
+ .displayOnNotFoundPage("Open API Schema document")
+ .requiresLegacyRedirect()
.blockingRoute()
- .nonApplicationRoute()
.build();
}
diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/OpenApiPathWithSegmentsTestCase.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/OpenApiPathWithSegmentsTestCase.java
index 40cc39f9d5576..3ea5304666de2 100644
--- a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/OpenApiPathWithSegmentsTestCase.java
+++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/OpenApiPathWithSegmentsTestCase.java
@@ -22,16 +22,16 @@ public class OpenApiPathWithSegmentsTestCase {
@Test
public void testOpenApiPathAccessResource() {
RestAssured.given().header("Accept", "application/yaml")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get(OPEN_API_PATH)
.then().header("Content-Type", "application/yaml;charset=UTF-8");
RestAssured.given().queryParam("format", "YAML")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get(OPEN_API_PATH)
.then().header("Content-Type", "application/yaml;charset=UTF-8");
RestAssured.given().header("Accept", "application/json")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get(OPEN_API_PATH)
.then().header("Content-Type", "application/json;charset=UTF-8");
RestAssured.given().queryParam("format", "JSON")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get(OPEN_API_PATH)
.then().header("Content-Type", "application/json;charset=UTF-8");
}
}
diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/OpenApiPathWithoutSegmentsTestCase.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/OpenApiPathWithoutSegmentsTestCase.java
index 36e0bd5644fa8..a8a34f7c2b17f 100644
--- a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/OpenApiPathWithoutSegmentsTestCase.java
+++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/OpenApiPathWithoutSegmentsTestCase.java
@@ -10,7 +10,7 @@
import io.restassured.RestAssured;
public class OpenApiPathWithoutSegmentsTestCase {
- private static final String OPEN_API_PATH = "/path-without-segments";
+ private static final String OPEN_API_PATH = "path-without-segments";
@RegisterExtension
static QuarkusUnitTest runner = new QuarkusUnitTest()
@@ -22,16 +22,16 @@ public class OpenApiPathWithoutSegmentsTestCase {
@Test
public void testOpenApiPathAccessResource() {
RestAssured.given().header("Accept", "application/yaml")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get("/q/" + OPEN_API_PATH)
.then().header("Content-Type", "application/yaml;charset=UTF-8");
RestAssured.given().queryParam("format", "YAML")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get("/q/" + OPEN_API_PATH)
.then().header("Content-Type", "application/yaml;charset=UTF-8");
RestAssured.given().header("Accept", "application/json")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get("/q/" + OPEN_API_PATH)
.then().header("Content-Type", "application/json;charset=UTF-8");
RestAssured.given().queryParam("format", "JSON")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get("/q/" + OPEN_API_PATH)
.then().header("Content-Type", "application/json;charset=UTF-8");
}
}
diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/SwaggerAndOpenAPIWithCommonPrefixTest.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/SwaggerAndOpenAPIWithCommonPrefixTest.java
index 5453c5c2b0f81..eeb12e9dc260f 100644
--- a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/SwaggerAndOpenAPIWithCommonPrefixTest.java
+++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/SwaggerAndOpenAPIWithCommonPrefixTest.java
@@ -20,7 +20,7 @@ public class SwaggerAndOpenAPIWithCommonPrefixTest {
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(OpenApiResource.class, ResourceBean.class)
- .addAsResource(new StringAsset("quarkus.smallrye-openapi.path=/swagger"), "application.properties"));
+ .addAsResource(new StringAsset("quarkus.smallrye-openapi.path=swagger"), "application.properties"));
@Test
public void shouldWorkEvenWithCommonPrefix() {
diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/spring/OpenApiPathWithSegmentsTestCase.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/spring/OpenApiPathWithSegmentsTestCase.java
index 95285a8b6624a..0229070b87efd 100644
--- a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/spring/OpenApiPathWithSegmentsTestCase.java
+++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/spring/OpenApiPathWithSegmentsTestCase.java
@@ -22,16 +22,16 @@ public class OpenApiPathWithSegmentsTestCase {
@Test
public void testOpenApiPathAccessResource() {
RestAssured.given().header("Accept", "application/yaml")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get(OPEN_API_PATH)
.then().header("Content-Type", "application/yaml;charset=UTF-8");
RestAssured.given().queryParam("format", "YAML")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get(OPEN_API_PATH)
.then().header("Content-Type", "application/yaml;charset=UTF-8");
RestAssured.given().header("Accept", "application/json")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get(OPEN_API_PATH)
.then().header("Content-Type", "application/json;charset=UTF-8");
RestAssured.given().queryParam("format", "JSON")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get(OPEN_API_PATH)
.then().header("Content-Type", "application/json;charset=UTF-8");
}
}
\ No newline at end of file
diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/spring/OpenApiPathWithoutSegmentsTestCase.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/spring/OpenApiPathWithoutSegmentsTestCase.java
index 1972db5393f88..d9094818eb9a0 100644
--- a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/spring/OpenApiPathWithoutSegmentsTestCase.java
+++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/spring/OpenApiPathWithoutSegmentsTestCase.java
@@ -10,7 +10,7 @@
import io.restassured.RestAssured;
public class OpenApiPathWithoutSegmentsTestCase {
- private static final String OPEN_API_PATH = "/path-without-segments";
+ private static final String OPEN_API_PATH = "path-without-segments";
@RegisterExtension
static QuarkusUnitTest runner = new QuarkusUnitTest()
@@ -22,16 +22,16 @@ public class OpenApiPathWithoutSegmentsTestCase {
@Test
public void testOpenApiPathAccessResource() {
RestAssured.given().header("Accept", "application/yaml")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get("/q/" + OPEN_API_PATH)
.then().header("Content-Type", "application/yaml;charset=UTF-8");
RestAssured.given().queryParam("format", "YAML")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get("/q/" + OPEN_API_PATH)
.then().header("Content-Type", "application/yaml;charset=UTF-8");
RestAssured.given().header("Accept", "application/json")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get("/q/" + OPEN_API_PATH)
.then().header("Content-Type", "application/json;charset=UTF-8");
RestAssured.given().queryParam("format", "JSON")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get("/q/" + OPEN_API_PATH)
.then().header("Content-Type", "application/json;charset=UTF-8");
}
}
\ No newline at end of file
diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/spring/SwaggerAndOpenAPIWithCommonPrefixTest.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/spring/SwaggerAndOpenAPIWithCommonPrefixTest.java
index a98a957550e5a..99973f8e17855 100644
--- a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/spring/SwaggerAndOpenAPIWithCommonPrefixTest.java
+++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/spring/SwaggerAndOpenAPIWithCommonPrefixTest.java
@@ -20,7 +20,7 @@ public class SwaggerAndOpenAPIWithCommonPrefixTest {
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClass(OpenApiController.class)
- .addAsResource(new StringAsset("quarkus.smallrye-openapi.path=/swagger"), "application.properties"));
+ .addAsResource(new StringAsset("quarkus.smallrye-openapi.path=swagger"), "application.properties"));
@Test
public void shouldWorkEvenWithCommonPrefix() {
diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/vertx/OpenApiPathWithSegmentsTestCase.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/vertx/OpenApiPathWithSegmentsTestCase.java
index 0fc066d70de20..b742edd7581c5 100644
--- a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/vertx/OpenApiPathWithSegmentsTestCase.java
+++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/vertx/OpenApiPathWithSegmentsTestCase.java
@@ -10,7 +10,7 @@
import io.restassured.RestAssured;
public class OpenApiPathWithSegmentsTestCase {
- private static final String OPEN_API_PATH = "/path/with/segments";
+ private static final String OPEN_API_PATH = "path/with/segments";
@RegisterExtension
static QuarkusUnitTest runner = new QuarkusUnitTest()
@@ -22,16 +22,16 @@ public class OpenApiPathWithSegmentsTestCase {
@Test
public void testOpenApiPathAccessResource() {
RestAssured.given().header("Accept", "application/yaml")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get("/q/" + OPEN_API_PATH)
.then().header("Content-Type", "application/yaml;charset=UTF-8");
RestAssured.given().queryParam("format", "YAML")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get("/q/" + OPEN_API_PATH)
.then().header("Content-Type", "application/yaml;charset=UTF-8");
RestAssured.given().header("Accept", "application/json")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get("/q/" + OPEN_API_PATH)
.then().header("Content-Type", "application/json;charset=UTF-8");
RestAssured.given().queryParam("format", "JSON")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get("/q/" + OPEN_API_PATH)
.then().header("Content-Type", "application/json;charset=UTF-8");
}
}
\ No newline at end of file
diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/vertx/OpenApiPathWithoutSegmentsTestCase.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/vertx/OpenApiPathWithoutSegmentsTestCase.java
index cd7f7152e1adc..ff26a8fd929c5 100644
--- a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/vertx/OpenApiPathWithoutSegmentsTestCase.java
+++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/vertx/OpenApiPathWithoutSegmentsTestCase.java
@@ -10,7 +10,7 @@
import io.restassured.RestAssured;
public class OpenApiPathWithoutSegmentsTestCase {
- private static final String OPEN_API_PATH = "/path-without-segments";
+ private static final String OPEN_API_PATH = "path-without-segments";
@RegisterExtension
static QuarkusUnitTest runner = new QuarkusUnitTest()
@@ -22,16 +22,16 @@ public class OpenApiPathWithoutSegmentsTestCase {
@Test
public void testOpenApiPathAccessResource() {
RestAssured.given().header("Accept", "application/yaml")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get("/q/" + OPEN_API_PATH)
.then().header("Content-Type", "application/yaml;charset=UTF-8");
RestAssured.given().queryParam("format", "YAML")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get("/q/" + OPEN_API_PATH)
.then().header("Content-Type", "application/yaml;charset=UTF-8");
RestAssured.given().header("Accept", "application/json")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get("/q/" + OPEN_API_PATH)
.then().header("Content-Type", "application/json;charset=UTF-8");
RestAssured.given().queryParam("format", "JSON")
- .when().get("/q" + OPEN_API_PATH)
+ .when().get("/q/" + OPEN_API_PATH)
.then().header("Content-Type", "application/json;charset=UTF-8");
}
}
\ No newline at end of file
diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/vertx/SwaggerAndOpenAPIWithCommonPrefixTest.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/vertx/SwaggerAndOpenAPIWithCommonPrefixTest.java
index ef74228ddd17f..20fc632d57b2b 100644
--- a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/vertx/SwaggerAndOpenAPIWithCommonPrefixTest.java
+++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/vertx/SwaggerAndOpenAPIWithCommonPrefixTest.java
@@ -20,7 +20,7 @@ public class SwaggerAndOpenAPIWithCommonPrefixTest {
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClass(OpenApiRoute.class)
- .addAsResource(new StringAsset("quarkus.smallrye-openapi.path=/swagger"), "application.properties"));
+ .addAsResource(new StringAsset("quarkus.smallrye-openapi.path=swagger"), "application.properties"));
@Test
public void shouldWorkEvenWithCommonPrefix() {
diff --git a/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiConfig.java b/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiConfig.java
index 789da6caa6110..ccb86c99987ab 100644
--- a/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiConfig.java
+++ b/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiConfig.java
@@ -19,7 +19,7 @@ public class SwaggerUiConfig {
*
* The value `/` is not allowed as it blocks the application from serving anything else.
*/
- @ConfigItem(defaultValue = "/swagger-ui")
+ @ConfigItem(defaultValue = "swagger-ui")
String path;
/**
diff --git a/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiProcessor.java b/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiProcessor.java
index 9fd5b0e8b1ee5..40c828dd7f9e3 100644
--- a/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiProcessor.java
+++ b/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiProcessor.java
@@ -93,9 +93,9 @@ public void getSwaggerUiFinalDestination(
"quarkus.swagger-ui.path was set to \"/\", this is not allowed as it blocks the application from serving anything else.");
}
- String openApiPath = httpRootPathBuildItem.adjustPath(nonApplicationRootPathBuildItem.adjustPath(openapi.path));
+ String openApiPath = httpRootPathBuildItem.resolvePath(nonApplicationRootPathBuildItem.resolvePath(openapi.path));
String swaggerUiPath = httpRootPathBuildItem
- .adjustPath(nonApplicationRootPathBuildItem.adjustPath(swaggerUiConfig.path));
+ .resolvePath(nonApplicationRootPathBuildItem.resolvePath(swaggerUiConfig.path));
AppArtifact artifact = WebJarUtil.getAppArtifact(curateOutcomeBuildItem, SWAGGER_UI_WEBJAR_GROUP_ID,
SWAGGER_UI_WEBJAR_ARTIFACT_ID);
@@ -108,8 +108,6 @@ public void getSwaggerUiFinalDestination(
generateIndexHtml(openApiPath, swaggerUiPath, swaggerUiConfig));
swaggerUiBuildProducer.produce(new SwaggerUiBuildItem(tempPath.toAbsolutePath().toString(), swaggerUiPath));
- displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(
- nonApplicationRootPathBuildItem.adjustPath(swaggerUiConfig.path + "/"), "Open API UI"));
// Handle live reload of branding files
if (liveReloadBuildItem.isLiveReload() && !liveReloadBuildItem.getChangedResources().isEmpty()) {
@@ -144,6 +142,7 @@ public void getSwaggerUiFinalDestination(
@Record(ExecutionTime.RUNTIME_INIT)
public void registerSwaggerUiHandler(SwaggerUiRecorder recorder,
BuildProducer routes,
+ NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
SwaggerUiBuildItem finalDestinationBuildItem,
SwaggerUiRuntimeConfig runtimeConfig,
LaunchModeBuildItem launchMode,
@@ -154,18 +153,17 @@ public void registerSwaggerUiHandler(SwaggerUiRecorder recorder,
finalDestinationBuildItem.getSwaggerUiPath(),
runtimeConfig);
- routes.produce(
- new RouteBuildItem.Builder()
- .route(swaggerUiConfig.path)
- .handler(handler)
- .nonApplicationRoute()
- .build());
- routes.produce(
- new RouteBuildItem.Builder()
- .route(swaggerUiConfig.path + "/*")
- .handler(handler)
- .nonApplicationRoute()
- .build());
+ routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
+ .route(swaggerUiConfig.path)
+ .displayOnNotFoundPage("Open API UI")
+ .handler(handler)
+ .requiresLegacyRedirect()
+ .build());
+ routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
+ .route(swaggerUiConfig.path + "/*")
+ .handler(handler)
+ .requiresLegacyRedirect()
+ .build());
}
}
diff --git a/extensions/swagger-ui/deployment/src/test/java/io/quarkus/swaggerui/deployment/CustomConfigTest.java b/extensions/swagger-ui/deployment/src/test/java/io/quarkus/swaggerui/deployment/CustomConfigTest.java
index f334a37646f06..7f29285cbddf0 100644
--- a/extensions/swagger-ui/deployment/src/test/java/io/quarkus/swaggerui/deployment/CustomConfigTest.java
+++ b/extensions/swagger-ui/deployment/src/test/java/io/quarkus/swaggerui/deployment/CustomConfigTest.java
@@ -20,7 +20,7 @@ public class CustomConfigTest {
@Test
public void shouldUseCustomConfig() {
- RestAssured.when().get("/q/custom").then().statusCode(200).body(containsString("/openapi"));
- RestAssured.when().get("/q/custom/index.html").then().statusCode(200).body(containsString("/openapi"));
+ RestAssured.when().get("/custom").then().statusCode(200).body(containsString("/openapi"));
+ RestAssured.when().get("/custom/index.html").then().statusCode(200).body(containsString("/openapi"));
}
}
diff --git a/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlConfig.java b/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlConfig.java
index 179f070cfb603..ecc6596581c90 100644
--- a/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlConfig.java
+++ b/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlConfig.java
@@ -26,7 +26,7 @@ public static class VertxGraphqlUiConfig {
*
* The value `/` is not allowed as it blocks the application from serving anything else.
*/
- @ConfigItem(defaultValue = "/graphql-ui")
+ @ConfigItem(defaultValue = "graphql-ui")
String path;
}
}
diff --git a/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlProcessor.java b/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlProcessor.java
index 1d80b073d28d5..08cce8ddce3ec 100644
--- a/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlProcessor.java
+++ b/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlProcessor.java
@@ -16,9 +16,9 @@
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.vertx.graphql.runtime.VertxGraphqlRecorder;
+import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
import io.quarkus.vertx.http.deployment.RouteBuildItem;
import io.quarkus.vertx.http.deployment.WebsocketSubProtocolsBuildItem;
-import io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.graphql.impl.GraphQLBatch;
@@ -50,7 +50,8 @@ List registerForReflection() {
@Record(ExecutionTime.STATIC_INIT)
void registerVertxGraphqlUI(VertxGraphqlRecorder recorder,
BuildProducer nativeResourcesProducer, VertxGraphqlConfig config,
- LaunchModeBuildItem launchMode, BuildProducer displayableEndpoints,
+ LaunchModeBuildItem launchMode,
+ NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
BuildProducer routes) {
boolean includeVertxGraphqlUi = launchMode.getLaunchMode().isDevOrTest() || config.ui.alwaysInclude;
@@ -66,10 +67,14 @@ void registerVertxGraphqlUI(VertxGraphqlRecorder recorder,
+ "\", this is not allowed as it blocks the application from serving anything else.");
}
- Handler handler = recorder.handler(path);
- routes.produce(new RouteBuildItem(path, handler));
- routes.produce(new RouteBuildItem(path + "/*", handler));
- displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(path + "/"));
+ Handler handler = recorder.handler();
+ routes.produce(nonApplicationRootPathBuildItem.routeBuilder().route(path).handler(handler)
+ .requiresLegacyRedirect()
+ .displayOnNotFoundPage("GraphQL UI", path + "/")
+ .build());
+ routes.produce(
+ nonApplicationRootPathBuildItem.routeBuilder().route(path + "/*").handler(handler).requiresLegacyRedirect()
+ .build());
nativeResourcesProducer.produce(new NativeImageResourceDirectoryBuildItem("io/vertx/ext/web/handler/graphiql"));
}
}
diff --git a/extensions/vertx-graphql/runtime/src/main/java/io/quarkus/vertx/graphql/runtime/VertxGraphqlRecorder.java b/extensions/vertx-graphql/runtime/src/main/java/io/quarkus/vertx/graphql/runtime/VertxGraphqlRecorder.java
index a720daf837cc3..1ed8d191ceb35 100644
--- a/extensions/vertx-graphql/runtime/src/main/java/io/quarkus/vertx/graphql/runtime/VertxGraphqlRecorder.java
+++ b/extensions/vertx-graphql/runtime/src/main/java/io/quarkus/vertx/graphql/runtime/VertxGraphqlRecorder.java
@@ -9,7 +9,7 @@
@Recorder
public class VertxGraphqlRecorder {
- public Handler handler(String path) {
+ public Handler handler() {
GraphiQLHandlerOptions options = new GraphiQLHandlerOptions();
options.setEnabled(true);
@@ -19,10 +19,11 @@ public Handler handler(String path) {
return new Handler() {
@Override
public void handle(RoutingContext event) {
- if (event.normalisedPath().length() == path.length()) {
-
+ if (event.normalisedPath().length() == (event.currentRoute().getPath().length()
+ + (event.mountPoint() == null ? 0 : event.mountPoint().length() - 1))) {
event.response().setStatusCode(302);
- event.response().headers().set(HttpHeaders.LOCATION, path + "/");
+ event.response().headers().set(HttpHeaders.LOCATION, (event.mountPoint() == null ? "" : event.mountPoint())
+ + event.currentRoute().getPath().substring(1) + "/");
event.response().end();
return;
}
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 91548d820f4b5..a1bf1775c2867 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
@@ -1,26 +1,68 @@
package io.quarkus.vertx.http.deployment;
+import java.net.URI;
+
import io.quarkus.builder.item.SimpleBuildItem;
+import io.quarkus.deployment.util.UriNormalizationUtil;
public final class HttpRootPathBuildItem extends SimpleBuildItem {
- private final String rootPath;
+ /**
+ * Normalized from quarkus.http.root-path.
+ * This path will always end in a slash
+ */
+ private final URI rootPath;
public HttpRootPathBuildItem(String rootPath) {
- this.rootPath = rootPath;
+ this.rootPath = UriNormalizationUtil.toURI(rootPath, true);
}
+ /**
+ * Return normalized Http root path configured from {@literal quarkus.http.root-path}.
+ * This path will always end in a slash.
+ *
+ * Use {@link #resolvePath(String)} if you need to construct a Uri from the Http root path.
+ *
+ * @return Normalized Http root path ending with a slash
+ * @see #resolvePath(String)
+ */
public String getRootPath() {
- return rootPath;
+ return rootPath.getPath();
}
+ /**
+ * Adjusts the path in relation to `quarkus.http.root-path`.
+ * Any leading slash will be removed to resolve relatively.
+ *
+ * @deprecated Use {@code resolvePath} instead. Do not use this method. Will be removed in Quarkus 2.0
+ */
public String adjustPath(String path) {
- if (!path.startsWith("/")) {
- throw new IllegalArgumentException("Path must start with /");
- }
- if (rootPath.equals("/")) {
- return path;
- }
- return rootPath + path;
+ return resolvePath(path.startsWith("/") ? path.substring(1) : path);
+ }
+
+ /**
+ * Resolve path into an absolute path.
+ * If path is relative, it will be resolved against `quarkus.http.root-path`.
+ * An absolute path will be normalized and returned.
+ *
+ * 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 /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 resolvePath(String path) {
+ return UriNormalizationUtil.normalizeWithBase(rootPath, path, false).getPath();
}
}
diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/NonApplicationRootPathBuildItem.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/NonApplicationRootPathBuildItem.java
index a2895ff8a1068..c6426ae14ebb5 100644
--- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/NonApplicationRootPathBuildItem.java
+++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/NonApplicationRootPathBuildItem.java
@@ -1,54 +1,319 @@
package io.quarkus.vertx.http.deployment;
+import java.net.URI;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
import io.quarkus.builder.item.SimpleBuildItem;
+import io.quarkus.deployment.util.UriNormalizationUtil;
+import io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem;
+import io.quarkus.vertx.http.runtime.HandlerType;
+import io.vertx.core.Handler;
+import io.vertx.ext.web.Route;
+import io.vertx.ext.web.Router;
+import io.vertx.ext.web.RoutingContext;
public final class NonApplicationRootPathBuildItem extends SimpleBuildItem {
- private final String httpRootPath;
- private final String frameworkRootPath;
- private final boolean separateRoot;
+ /**
+ * Normalized of quarkus.http.root-path.
+ * Must end in a slash
+ */
+ final URI httpRootPath;
+
+ /**
+ * Normalized from quarkus.http.non-application-root-path
+ */
+ final URI nonApplicationRootPath;
+
+ /**
+ * Non-Application root path is distinct from HTTP root path.
+ */
+ final boolean dedicatedRouterRequired;
+
+ final boolean attachedToMainRouter;
+
+ public NonApplicationRootPathBuildItem(String httpRootPath, String nonApplicationRootPath) {
+ // Presume value always starts with a slash and is normalized
+ this.httpRootPath = UriNormalizationUtil.toURI(httpRootPath, true);
+
+ this.nonApplicationRootPath = UriNormalizationUtil.normalizeWithBase(this.httpRootPath, nonApplicationRootPath,
+ true);
+
+ this.dedicatedRouterRequired = !this.nonApplicationRootPath.getPath().equals(this.httpRootPath.getPath());
+
+ // Is the non-application root path underneath the http root path. Do we add non-application root to main router or not.
+ this.attachedToMainRouter = this.nonApplicationRootPath.getPath().startsWith(this.httpRootPath.getPath());
+ }
+
+ /**
+ * Is a dedicated router required for non-application endpoints.
+ *
+ * @return boolean
+ */
+ public boolean isDedicatedRouterRequired() {
+ return dedicatedRouterRequired;
+ }
- public NonApplicationRootPathBuildItem(String frameworkRootPath) {
- this(frameworkRootPath, null);
+ public boolean isAttachedToMainRouter() {
+ return attachedToMainRouter;
}
- public NonApplicationRootPathBuildItem(String frameworkRootPath, String httpRootPath) {
- this.frameworkRootPath = frameworkRootPath;
- this.separateRoot = frameworkRootPath != null
- && !frameworkRootPath.equals("")
- && !frameworkRootPath.equals("/");
- this.httpRootPath = httpRootPath;
+ /**
+ * Path to the Non-application root for use with Vert.x Routers,
+ * has a leading slash.
+ *
+ * If it's under the HTTP Root, return a path relative to HTTP Root.
+ * Otherwise, return an absolute path.
+ *
+ * @return String Path suitable for use with Vert.x Router. It has a leading slash
+ */
+ String getVertxRouterPath() {
+ if (attachedToMainRouter) {
+ return "/" + relativize(httpRootPath.getPath(), nonApplicationRootPath.getPath());
+ } else {
+ return getNonApplicationRootPath();
+ }
}
- public String getFrameworkRootPath() {
- return frameworkRootPath;
+ String relativize(String rootPath, String leafPath) {
+ if (leafPath.startsWith(rootPath)) {
+ return leafPath.substring(rootPath.length());
+ }
+
+ return null;
}
- public boolean isSeparateRoot() {
- return separateRoot;
+ public String getNormalizedHttpRootPath() {
+ return httpRootPath.getPath();
}
/**
- * Adjusts a path by including the non-application root path.
+ * Return normalized root path configured from {@literal quarkus.http.root-path}
+ * and {quarkus.http.non-application-root-path}.
+ * This path will always end in a slash.
+ *
+ * Use {@link #resolvePath(String)} if you need to construct a URI for
+ * a non-application endpoint.
+ *
+ * @return Normalized non-application root path ending with a slash
+ * @see #resolvePath(String)
*/
- public String adjustPath(String path) {
- if (!path.startsWith("/")) {
- throw new IllegalArgumentException("Path must start with /");
+ public String getNonApplicationRootPath() {
+ return nonApplicationRootPath.getPath();
+ }
+
+ /**
+ * Resolve path into an absolute path.
+ * If path is relative, it will be resolved against `quarkus.http.non-application-root-path`.
+ * An absolute path will be normalized and returned.
+ *
+ * Given {@literal quarkus.http.root-path=/} and
+ * {@literal quarkus.http.non-application-root-path="q"}
+ *
+ *
{@code resolvePath("foo")} will return {@literal /q/foo}
+ *
{@code resolvePath("/foo")} will return {@literal /foo}
+ *
+ *
+ * Given {@literal quarkus.http.root-path=/} and
+ * {@literal quarkus.http.non-application-root-path="/q"}
+ *
+ *
{@code resolvePath("foo")} will return {@literal /q/foo}
+ *
{@code resolvePath("/foo")} will return {@literal /foo}
+ *
+ * Given {@literal quarkus.http.root-path=/app} and
+ * {@literal quarkus.http.non-application-root-path="q"}
+ *
+ *
{@code resolvePath("foo")} will return {@literal /app/q/foo}
+ *
{@code resolvePath("/foo")} will return {@literal /foo}
+ *
+ * Given {@literal quarkus.http.root-path=/app} and
+ * {@literal quarkus.http.non-application-root-path="/q"}
+ *
+ *
{@code resolvePath("foo")} will return {@literal /q/foo}
+ *
{@code resolvePath("/foo")} will return {@literal /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)
+ * @throws IllegalArgumentException if path is null or empty
+ */
+ public String resolvePath(String path) {
+ if (path == null || path.trim().isEmpty()) {
+ throw new IllegalArgumentException("Specified path can not be empty");
}
- if (frameworkRootPath.equals("/")) {
- return path;
+ return UriNormalizationUtil.normalizeWithBase(nonApplicationRootPath, path, false).getPath();
+ }
+
+ /**
+ * Resolve a base path and a sub-resource against the non-application root.
+ * This will call resolvePath on the base path (to establish a fully-resolved,
+ * absolute path), and then will resolve the subRoute against that resolved path.
+ * This allows a configured subpath to be configured (consistently)
+ * as an absolute URI.
+ *
+ * Given {@literal quarkus.http.root-path=/} and
+ * {@literal quarkus.http.non-application-root-path="q"}
+ *
+ *
{@code resolveNestedPath("foo", "a")} will return {@literal /q/foo/a}
+ *
{@code resolveNestedPath("foo", "/a)} will return {@literal /a}
+ *
+ *
+ * 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)
+ * @see #resolvePath(String)
+ * @throws IllegalArgumentException if path is null or empty
+ */
+ public String resolveNestedPath(String path, String subRoute) {
+ if (path == null || path.trim().isEmpty()) {
+ throw new IllegalArgumentException("Specified path can not be empty");
}
- return frameworkRootPath + path;
+ URI base = UriNormalizationUtil.normalizeWithBase(nonApplicationRootPath, path, true);
+ return UriNormalizationUtil.normalizeWithBase(base, subRoute, false).getPath();
+ }
+
+ public Builder routeBuilder() {
+ return new Builder(this);
}
/**
- * Adjusts a path by including both the non-application root path and
- * the HTTP root path.
+ * Per non-application endpoint instance.
*/
- public String adjustPathIncludingHttpRootPath(String path) {
- String withFrameWorkPath = adjustPath(path);
- if (httpRootPath == null || httpRootPath.equals("/")) {
- return withFrameWorkPath;
+ public static class Builder extends RouteBuildItem.Builder {
+ private final NonApplicationRootPathBuildItem buildItem;
+ private boolean requiresLegacyRedirect = false;
+ private RouteBuildItem.RouteType routeType = RouteBuildItem.RouteType.FRAMEWORK_ROUTE;
+ private String path;
+ private String absolute;
+
+ Builder(NonApplicationRootPathBuildItem buildItem) {
+ this.buildItem = buildItem;
+ }
+
+ @Override
+ public Builder routeFunction(Function routeFunction) {
+ throw new RuntimeException("This method is not supported for non-application routes");
+ }
+
+ public Builder routeFunction(String route, Consumer routeFunction) {
+ String temp = route;
+ route = absolute = buildItem.resolvePath(route);
+
+ boolean isFrameworkRoute = buildItem.dedicatedRouterRequired
+ && route.startsWith(buildItem.getNonApplicationRootPath());
+
+ if (isFrameworkRoute) {
+ // relative non-application root
+ this.path = "/" + buildItem.relativize(buildItem.getNonApplicationRootPath(), route);
+ this.routeType = RouteBuildItem.RouteType.FRAMEWORK_ROUTE;
+ } else if (route.startsWith(buildItem.httpRootPath.getPath())) {
+ // relative to http root
+ this.path = "/" + buildItem.relativize(buildItem.httpRootPath.getPath(), route);
+ this.routeType = RouteBuildItem.RouteType.APPLICATION_ROUTE;
+ } else if (route.startsWith("/")) {
+ // absolute path
+ this.path = route;
+ this.routeType = RouteBuildItem.RouteType.ABSOLUTE_ROUTE;
+ }
+
+ super.routeFunction(this.path, routeFunction);
+ return this;
+ }
+
+ @Override
+ public Builder route(String route) {
+ routeFunction(route, null);
+ return this;
+ }
+
+ public Builder nestedRoute(String baseRoute, String subRoute) {
+ if (subRoute.startsWith("/")) {
+ routeFunction(subRoute, null);
+ return this;
+ }
+
+ baseRoute = baseRoute.endsWith("/") ? baseRoute : baseRoute + "/";
+ routeFunction(baseRoute + subRoute, null);
+ return this;
+ }
+
+ /**
+ * @deprecated This will be removed in Quarkus 2.0, don't use unless you have to.
+ */
+ @Deprecated
+ public Builder requiresLegacyRedirect() {
+ this.requiresLegacyRedirect = true;
+ return this;
+ }
+
+ @Override
+ public Builder handler(Handler handler) {
+ super.handler(handler);
+ return this;
+ }
+
+ @Override
+ public Builder handlerType(HandlerType handlerType) {
+ super.handlerType(handlerType);
+ return this;
+ }
+
+ @Override
+ public Builder blockingRoute() {
+ super.blockingRoute();
+ return this;
+ }
+
+ @Override
+ public Builder failureRoute() {
+ super.failureRoute();
+ return this;
+ }
+
+ @Override
+ public Builder displayOnNotFoundPage() {
+ this.displayOnNotFoundPage = true;
+ return this;
+ }
+
+ @Override
+ public Builder displayOnNotFoundPage(String notFoundPageTitle) {
+ this.displayOnNotFoundPage = true;
+ this.notFoundPageTitle = notFoundPageTitle;
+ return this;
+ }
+
+ @Override
+ public Builder displayOnNotFoundPage(String notFoundPageTitle, String notFoundPagePath) {
+ this.displayOnNotFoundPage = true;
+ this.notFoundPageTitle = notFoundPageTitle;
+ this.notFoundPagePath = notFoundPagePath;
+ return this;
+ }
+
+ public RouteBuildItem build() {
+ // If path is same as absolute, we don't enable legacy redirect
+ if (requiresLegacyRedirect && path.equals(absolute)) {
+ requiresLegacyRedirect = false;
+ }
+ return new RouteBuildItem(this, routeType, requiresLegacyRedirect);
+ }
+
+ @Override
+ protected NotFoundPageDisplayableEndpointBuildItem getNotFoundEndpoint() {
+ if (!displayOnNotFoundPage) {
+ return null;
+ }
+ if (notFoundPagePath == null) {
+ throw new RuntimeException("Cannot display " + routeFunction
+ + " on not found page as no explicit path was specified and a route function is in use");
+ }
+ return new NotFoundPageDisplayableEndpointBuildItem(absolute, notFoundPageTitle, true);
}
- return httpRootPath + withFrameWorkPath;
}
}
diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RouteBuildItem.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RouteBuildItem.java
index c2074e271ff25..2f732f16c473e 100644
--- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RouteBuildItem.java
+++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RouteBuildItem.java
@@ -1,8 +1,10 @@
package io.quarkus.vertx.http.deployment;
+import java.util.function.Consumer;
import java.util.function.Function;
import io.quarkus.builder.item.MultiBuildItem;
+import io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem;
import io.quarkus.vertx.http.runtime.BasicRoute;
import io.quarkus.vertx.http.runtime.HandlerType;
import io.vertx.core.Handler;
@@ -19,8 +21,9 @@ public static Builder builder() {
private final Function routeFunction;
private final Handler handler;
private final HandlerType type;
- private final boolean frameworkRoute;
+ private final RouteType routeType;
private final boolean requiresLegacyRedirect;
+ private final NotFoundPageDisplayableEndpointBuildItem notFoundPageDisplayableEndpoint;
/**
* @deprecated Use the Builder instead.
@@ -30,8 +33,9 @@ public RouteBuildItem(Function routeFunction, Handler handler) {
this(new BasicRoute(route), handler);
}
- private RouteBuildItem(Builder builder) {
+ RouteBuildItem(Builder builder, RouteType routeType, boolean requiresLegacyRedirect) {
this.routeFunction = builder.routeFunction;
this.handler = builder.handler;
this.type = builder.type;
- this.frameworkRoute = builder.frameworkRoute;
- this.requiresLegacyRedirect = builder.requiresLegacyRedirect;
+ this.routeType = routeType;
+ this.requiresLegacyRedirect = requiresLegacyRedirect;
+ this.notFoundPageDisplayableEndpoint = builder.getNotFoundEndpoint();
}
public Handler getHandler() {
@@ -95,27 +100,64 @@ public Function getRouteFunction() {
}
public boolean isFrameworkRoute() {
- return frameworkRoute;
+ return routeType.equals(RouteType.FRAMEWORK_ROUTE);
+ }
+
+ public boolean isApplicationRoute() {
+ return routeType.equals(RouteType.APPLICATION_ROUTE);
+ }
+
+ public boolean isAbsoluteRoute() {
+ return routeType.equals(RouteType.ABSOLUTE_ROUTE);
}
public boolean isRequiresLegacyRedirect() {
return requiresLegacyRedirect;
}
- public static class Builder {
- private Function routeFunction;
- private Handler handler;
- private HandlerType type = HandlerType.NORMAL;
- private boolean frameworkRoute = false;
- private boolean requiresLegacyRedirect = false;
+ public NotFoundPageDisplayableEndpointBuildItem getNotFoundPageDisplayableEndpoint() {
+ return notFoundPageDisplayableEndpoint;
+ }
+
+ public enum RouteType {
+ FRAMEWORK_ROUTE,
+ APPLICATION_ROUTE,
+ ABSOLUTE_ROUTE
+ }
+ /**
+ * NonApplicationRootPathBuildItem.Builder extends this.
+ * Please verify the extended builders behavior when changing this one.
+ */
+ public static class Builder {
+ protected Function routeFunction;
+ protected Handler handler;
+ protected HandlerType type = HandlerType.NORMAL;
+ protected boolean displayOnNotFoundPage;
+ protected String notFoundPageTitle;
+ protected String notFoundPagePath;
+
+ /**
+ * {@link #routeFunction(String, Consumer)} should be used instead
+ *
+ * @param routeFunction
+ * @return
+ */
+ @Deprecated
public Builder routeFunction(Function routeFunction) {
this.routeFunction = routeFunction;
return this;
}
+ public Builder routeFunction(String path, Consumer routeFunction) {
+ this.routeFunction = new BasicRoute(path, null, routeFunction);
+ this.notFoundPagePath = path;
+ return this;
+ }
+
public Builder route(String route) {
this.routeFunction = new BasicRoute(route);
+ this.notFoundPagePath = route;
return this;
}
@@ -139,20 +181,37 @@ public Builder failureRoute() {
return this;
}
- public Builder nonApplicationRoute() {
- this.frameworkRoute = true;
- this.requiresLegacyRedirect = true;
+ public Builder displayOnNotFoundPage() {
+ this.displayOnNotFoundPage = true;
return this;
}
- public Builder nonApplicationRoute(boolean requiresLegacyRedirect) {
- this.frameworkRoute = true;
- this.requiresLegacyRedirect = requiresLegacyRedirect;
+ public Builder displayOnNotFoundPage(String notFoundPageTitle) {
+ this.displayOnNotFoundPage = true;
+ this.notFoundPageTitle = notFoundPageTitle;
+ return this;
+ }
+
+ public Builder displayOnNotFoundPage(String notFoundPageTitle, String notFoundPagePath) {
+ this.displayOnNotFoundPage = true;
+ this.notFoundPageTitle = notFoundPageTitle;
+ this.notFoundPagePath = notFoundPagePath;
return this;
}
public RouteBuildItem build() {
- return new RouteBuildItem(this);
+ return new RouteBuildItem(this, RouteType.APPLICATION_ROUTE, false);
+ }
+
+ protected NotFoundPageDisplayableEndpointBuildItem getNotFoundEndpoint() {
+ if (!displayOnNotFoundPage) {
+ return null;
+ }
+ if (notFoundPagePath == null) {
+ throw new RuntimeException("Cannot display " + routeFunction
+ + " on not found page as no explicit path was specified and a route function is in use");
+ }
+ return new NotFoundPageDisplayableEndpointBuildItem(notFoundPagePath, notFoundPageTitle);
}
}
}
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 5d8663b8000fe..dd39d2a01b3a2 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
@@ -7,6 +7,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.CodeSource;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@@ -46,6 +47,7 @@
import io.quarkus.vertx.core.deployment.CoreVertxBuildItem;
import io.quarkus.vertx.core.deployment.EventLoopCountBuildItem;
import io.quarkus.vertx.http.deployment.devmode.HttpRemoteDevClientProvider;
+import io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem;
import io.quarkus.vertx.http.runtime.CurrentVertxRequest;
import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig;
import io.quarkus.vertx.http.runtime.HttpConfiguration;
@@ -80,7 +82,7 @@ HttpRootPathBuildItem httpRoot(HttpBuildTimeConfig httpBuildTimeConfig) {
@BuildStep
NonApplicationRootPathBuildItem frameworkRoot(HttpBuildTimeConfig httpBuildTimeConfig) {
- return new NonApplicationRootPathBuildItem(httpBuildTimeConfig.nonApplicationRootPath, httpBuildTimeConfig.rootPath);
+ return new NonApplicationRootPathBuildItem(httpBuildTimeConfig.rootPath, httpBuildTimeConfig.nonApplicationRootPath);
}
@BuildStep
@@ -117,6 +119,18 @@ public KubernetesPortBuildItem kubernetes() {
return new KubernetesPortBuildItem(port, "http");
}
+ @BuildStep
+ void notRoundRoutes(
+ List routes,
+ BuildProducer notFound) {
+ for (RouteBuildItem i : routes) {
+ if (i.getNotFoundPageDisplayableEndpoint() != null) {
+ notFound.produce(i.getNotFoundPageDisplayableEndpoint());
+ }
+
+ }
+ }
+
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
VertxWebRouterBuildItem initializeRouter(VertxHttpRecorder recorder,
@@ -124,35 +138,54 @@ VertxWebRouterBuildItem initializeRouter(VertxHttpRecorder recorder,
List routes,
HttpBuildTimeConfig httpBuildTimeConfig,
NonApplicationRootPathBuildItem nonApplicationRootPath,
- BuildProducer frameworkRouterBuildProducer,
ShutdownContextBuildItem shutdown) {
- RuntimeValue router = recorder.initializeRouter(vertx.getVertx());
- RuntimeValue frameworkRouter = recorder.initializeRouter(vertx.getVertx());
- boolean frameworkRouterFound = false;
- recorder.setNonApplicationRedirectHandler(nonApplicationRootPath.getFrameworkRootPath(), httpBuildTimeConfig.rootPath);
+ RuntimeValue httpRouteRouter = recorder.initializeRouter(vertx.getVertx());
+ RuntimeValue frameworkRouter = null;
+ RuntimeValue mainRouter = null;
+
+ List redirectRoutes = new ArrayList<>();
+ boolean frameworkRouterCreated = false;
+ boolean mainRouterCreated = false;
for (RouteBuildItem route : routes) {
- if (nonApplicationRootPath.isSeparateRoot() && route.isFrameworkRoute()) {
- frameworkRouterFound = true;
+ if (nonApplicationRootPath.isDedicatedRouterRequired() && route.isFrameworkRoute()) {
+ // Non-application endpoints on a separate path
+ if (!frameworkRouterCreated) {
+ frameworkRouter = recorder.initializeRouter(vertx.getVertx());
+ frameworkRouterCreated = true;
+ }
+
recorder.addRoute(frameworkRouter, route.getRouteFunction(), route.getHandler(), route.getType());
- // Handle redirects from old paths to new non application endpoint root
if (httpBuildTimeConfig.redirectToNonApplicationRootPath && route.isRequiresLegacyRedirect()) {
- recorder.addRoute(router, route.getRouteFunction(),
- recorder.getNonApplicationRedirectHandler(),
- route.getType());
+ redirectRoutes.add(route);
+ }
+ } else if (route.isAbsoluteRoute()) {
+ // Add Route to "/"
+ if (!mainRouterCreated) {
+ mainRouter = recorder.initializeRouter(vertx.getVertx());
+ mainRouterCreated = true;
}
+ recorder.addRoute(mainRouter, route.getRouteFunction(), route.getHandler(), route.getType());
} else {
- recorder.addRoute(router, route.getRouteFunction(), route.getHandler(), route.getType());
+ // Add Route to "/${quarkus.http.root-path}/
+ recorder.addRoute(httpRouteRouter, route.getRouteFunction(), route.getHandler(), route.getType());
}
}
- if (frameworkRouterFound) {
- frameworkRouterBuildProducer.produce(new VertxNonApplicationRouterBuildItem(frameworkRouter));
+ if (frameworkRouterCreated) {
+ if (redirectRoutes.size() > 0) {
+ recorder.setNonApplicationRedirectHandler(nonApplicationRootPath.getNonApplicationRootPath(),
+ nonApplicationRootPath.getNormalizedHttpRootPath());
+
+ redirectRoutes.forEach(route -> recorder.addRoute(httpRouteRouter, route.getRouteFunction(),
+ recorder.getNonApplicationRedirectHandler(),
+ route.getType()));
+ }
}
- return new VertxWebRouterBuildItem(router);
+ return new VertxWebRouterBuildItem(httpRouteRouter, mainRouter, frameworkRouter);
}
@BuildStep
@@ -167,8 +200,7 @@ ServiceStartBuildItem finalizeRouter(
VertxHttpRecorder recorder, BeanContainerBuildItem beanContainer, CoreVertxBuildItem vertx,
LaunchModeBuildItem launchMode,
List defaultRoutes, List filters,
- VertxWebRouterBuildItem router,
- Optional frameworkRouter,
+ VertxWebRouterBuildItem httpRouteRouter,
NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
HttpBuildTimeConfig httpBuildTimeConfig, HttpConfiguration httpConfiguration,
List requireBodyHandlerBuildItems,
@@ -204,15 +236,30 @@ ServiceStartBuildItem finalizeRouter(
Handler bodyHandler = !requireBodyHandlerBuildItems.isEmpty() ? bodyHandlerBuildItem.getHandler()
: null;
- if (frameworkRouter.isPresent()) {
- recorder.mountFrameworkRouter(router.getRouter(),
- frameworkRouter.get().getRouter(),
- nonApplicationRootPathBuildItem.getFrameworkRootPath());
+ Optional> mainRouter = httpRouteRouter.getMainRouter() != null
+ ? Optional.of(httpRouteRouter.getMainRouter())
+ : Optional.empty();
+
+ if (httpRouteRouter.getFrameworkRouter() != null) {
+ if (nonApplicationRootPathBuildItem.isAttachedToMainRouter()) {
+ // Mount nested framework router
+ recorder.mountFrameworkRouter(httpRouteRouter.getHttpRouter(),
+ httpRouteRouter.getFrameworkRouter(),
+ nonApplicationRootPathBuildItem.getVertxRouterPath());
+ } else {
+ // Create main router, not mounted under application router
+ if (!mainRouter.isPresent()) {
+ mainRouter = Optional.of(recorder.initializeRouter(vertx.getVertx()));
+ }
+ // Mount independent framework router under new main router
+ recorder.mountFrameworkRouter(mainRouter.get(), httpRouteRouter.getFrameworkRouter(),
+ nonApplicationRootPathBuildItem.getVertxRouterPath());
+ }
}
recorder.finalizeRouter(beanContainer.getValue(),
defaultRoute.map(DefaultRouteBuildItem::getRoute).orElse(null),
- listOfFilters, vertx.getVertx(), lrc, router.getRouter(), httpBuildTimeConfig.rootPath,
+ listOfFilters, vertx.getVertx(), lrc, mainRouter, httpRouteRouter.getHttpRouter(), httpBuildTimeConfig.rootPath,
launchMode.getLaunchMode(),
!requireBodyHandlerBuildItems.isEmpty(), bodyHandler, httpConfiguration, gracefulShutdownFilter,
shutdownConfig, executorBuildItem.getExecutorProxy());
diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxNonApplicationRouterBuildItem.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxNonApplicationRouterBuildItem.java
deleted file mode 100644
index a1a97109ef9d7..0000000000000
--- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxNonApplicationRouterBuildItem.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package io.quarkus.vertx.http.deployment;
-
-import io.quarkus.builder.item.SimpleBuildItem;
-import io.quarkus.runtime.RuntimeValue;
-import io.vertx.ext.web.Router;
-
-public final class VertxNonApplicationRouterBuildItem extends SimpleBuildItem {
-
- private RuntimeValue router;
-
- VertxNonApplicationRouterBuildItem(RuntimeValue router) {
- this.router = router;
- }
-
- public RuntimeValue getRouter() {
- return router;
- }
-}
diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxWebRouterBuildItem.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxWebRouterBuildItem.java
index b8f559340854a..b3c9a7a064564 100644
--- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxWebRouterBuildItem.java
+++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxWebRouterBuildItem.java
@@ -6,13 +6,37 @@
public final class VertxWebRouterBuildItem extends SimpleBuildItem {
- private RuntimeValue router;
+ private RuntimeValue httpRouter;
+ private RuntimeValue mainRouter;
+ private RuntimeValue frameworkRouter;
- VertxWebRouterBuildItem(RuntimeValue router) {
- this.router = router;
+ VertxWebRouterBuildItem(RuntimeValue httpRouter, RuntimeValue mainRouter,
+ RuntimeValue frameworkRouter) {
+ this.httpRouter = httpRouter;
+ this.mainRouter = mainRouter;
+ this.frameworkRouter = frameworkRouter;
}
- public RuntimeValue getRouter() {
- return router;
+ public RuntimeValue getHttpRouter() {
+ return httpRouter;
+ }
+
+ /**
+ * Will be {@code null} if `${quarkus.http.root-path}` is {@literal /}.
+ *
+ * @return RuntimeValue
+ */
+ RuntimeValue getMainRouter() {
+ return mainRouter;
+ }
+
+ /**
+ * Will be {@code null} if {@code ${quarkus.http.root-path}} is the same as
+ * {@code ${quarkus.http.non-application-root-path}}.
+ *
+ * @return RuntimeValue
+ */
+ RuntimeValue getFrameworkRouter() {
+ return frameworkRouter;
}
}
diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/ArcDevProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/ArcDevProcessor.java
index 1de137cccc47e..90a9a0df72eea 100644
--- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/ArcDevProcessor.java
+++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/ArcDevProcessor.java
@@ -22,19 +22,26 @@ void registerRoutes(ArcConfig arcConfig, ArcDevRecorder recorder,
BuildProducer routes,
BuildProducer displayableEndpoints,
NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) {
- String basePath = nonApplicationRootPathBuildItem.adjustPath("/arc");
+ String basePath = "arc";
String beansPath = basePath + "/beans";
String removedBeansPath = basePath + "/removed-beans";
String observersPath = basePath + "/observers";
- routes.produce(RouteBuildItem.builder().route(basePath)
+ routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
+ .route(basePath)
+ .displayOnNotFoundPage("CDI Overview")
.handler(recorder.createSummaryHandler(getConfigProperties(arcConfig))).build());
- routes.produce(RouteBuildItem.builder().route(beansPath).handler(recorder.createBeansHandler()).build());
- routes.produce(RouteBuildItem.builder().route(removedBeansPath).handler(recorder.createRemovedBeansHandler()).build());
- routes.produce(RouteBuildItem.builder().route(observersPath).handler(recorder.createObserversHandler()).build());
- displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(basePath));
- displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(beansPath));
- displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(removedBeansPath));
- displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(observersPath));
+ routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
+ .route(beansPath)
+ .displayOnNotFoundPage("Active CDI Beans")
+ .handler(recorder.createBeansHandler()).build());
+ routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
+ .route(removedBeansPath)
+ .displayOnNotFoundPage("Removed CDI Beans")
+ .handler(recorder.createRemovedBeansHandler()).build());
+ routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
+ .route(observersPath)
+ .displayOnNotFoundPage("Active CDI Observers")
+ .handler(recorder.createObserversHandler()).build());
}
// Note that we can't turn ArcConfig into BUILD_AND_RUN_TIME_FIXED because it's referencing IndexDependencyConfig
diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/NotFoundPageDisplayableEndpointBuildItem.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/NotFoundPageDisplayableEndpointBuildItem.java
index 92b0387ab839a..a0d88468946d6 100644
--- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/NotFoundPageDisplayableEndpointBuildItem.java
+++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/NotFoundPageDisplayableEndpointBuildItem.java
@@ -1,14 +1,22 @@
package io.quarkus.vertx.http.deployment.devmode;
import io.quarkus.builder.item.MultiBuildItem;
+import io.quarkus.runtime.TemplateHtmlBuilder;
+import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem;
final public class NotFoundPageDisplayableEndpointBuildItem extends MultiBuildItem {
private final String endpoint;
private final String description;
+ private final boolean absolutePath;
public NotFoundPageDisplayableEndpointBuildItem(String endpoint, String description) {
+ this(endpoint, description, false);
+ }
+
+ public NotFoundPageDisplayableEndpointBuildItem(String endpoint, String description, boolean absolutePath) {
this.endpoint = endpoint;
this.description = description;
+ this.absolutePath = absolutePath;
}
public NotFoundPageDisplayableEndpointBuildItem(String endpoint) {
@@ -22,4 +30,16 @@ public String getEndpoint() {
public String getDescription() {
return description;
}
+
+ public boolean isAbsolutePath() {
+ return absolutePath;
+ }
+
+ public String getEndpoint(HttpRootPathBuildItem httpRoot) {
+ if (absolutePath) {
+ return endpoint;
+ } else {
+ return TemplateHtmlBuilder.adjustRoot(httpRoot.getRootPath(), endpoint);
+ }
+ }
}
diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java
index ba8f7787d573c..727246f3ee6e2 100644
--- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java
+++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java
@@ -76,10 +76,8 @@
import io.quarkus.qute.ValueResolvers;
import io.quarkus.qute.Variant;
import io.quarkus.runtime.RuntimeValue;
-import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem;
import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
import io.quarkus.vertx.http.deployment.RouteBuildItem;
-import io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem;
import io.quarkus.vertx.http.runtime.devmode.DevConsoleFilter;
import io.quarkus.vertx.http.runtime.devmode.DevConsoleRecorder;
import io.quarkus.vertx.http.runtime.devmode.RedirectHandler;
@@ -185,15 +183,12 @@ public void handle(HttpServerRequest event) {
}
protected static void newRouter(Engine engine,
- HttpRootPathBuildItem httpRootPathBuildItem,
NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) {
- // "" or "/myroot"
- String httpRootPath = httpRootPathBuildItem.adjustPath("/");
- httpRootPath = httpRootPath.substring(0, httpRootPath.lastIndexOf("/"));
- // "" or "/myroot" or "/q" or "/myroot/q"
- String frameworkRootPath = httpRootPathBuildItem.adjustPath(nonApplicationRootPathBuildItem.adjustPath("/"));
- frameworkRootPath = frameworkRootPath.substring(0, frameworkRootPath.lastIndexOf("/"));
+ // "/" or "/myroot/"
+ String httpRootPath = nonApplicationRootPathBuildItem.getNormalizedHttpRootPath();
+ // "/" or "/myroot/" or "/q/" or "/myroot/q/"
+ String frameworkRootPath = nonApplicationRootPathBuildItem.getNonApplicationRootPath();
Handler errorHandler = new Handler() {
@Override
@@ -211,15 +206,12 @@ public void handle(RoutingContext event) {
.handler(new DevConsole(engine, httpRootPath, frameworkRootPath));
mainRouter = Router.router(devConsoleVertx);
mainRouter.errorHandler(500, errorHandler);
- mainRouter.route(httpRootPathBuildItem.adjustPath(nonApplicationRootPathBuildItem.adjustPath("/dev/*")))
- .subRouter(router);
+ mainRouter.route(nonApplicationRootPathBuildItem.resolvePath("dev/*")).subRouter(router);
}
@BuildStep(onlyIf = IsDevelopment.class)
public ServiceStartBuildItem buildTimeTemplates(List items,
- BuildProducer devTemplatePaths,
CurateOutcomeBuildItem curateOutcomeBuildItem) {
- collectTemplates(devTemplatePaths);
Map> results = new HashMap<>();
for (DevConsoleTemplateInfoBuildItem i : items) {
Entry groupAndArtifact = i.groupIdAndArtifactId(curateOutcomeBuildItem);
@@ -276,31 +268,30 @@ public HistoryHandlerBuildItem hander(BuildProducer logHand
public void setupActions(List routes,
BuildProducer routeBuildItemBuildProducer,
List devTemplatePaths,
- Optional devTemplateVariants,
LogStreamRecorder recorder,
CurateOutcomeBuildItem curateOutcomeBuildItem,
- HttpRootPathBuildItem httpRootPathBuildItem,
HistoryHandlerBuildItem historyHandlerBuildItem,
NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) {
initializeVirtual();
- newRouter(buildEngine(devTemplatePaths), httpRootPathBuildItem, nonApplicationRootPathBuildItem);
+ newRouter(buildEngine(devTemplatePaths), nonApplicationRootPathBuildItem);
// Add the log stream
- routeBuildItemBuildProducer.produce(new RouteBuildItem.Builder()
- .route("/dev/logstream")
+ routeBuildItemBuildProducer.produce(nonApplicationRootPathBuildItem.routeBuilder()
+ .route("dev/logstream")
.handler(recorder.websocketHandler(historyHandlerBuildItem.value))
- .nonApplicationRoute(false)
.build());
for (DevConsoleRouteBuildItem i : routes) {
Entry groupAndArtifact = i.groupIdAndArtifactId(curateOutcomeBuildItem);
// if the handler is a proxy, then that means it's been produced by a recorder and therefore belongs in the regular runtime Vert.x instance
if (i.getHandler() instanceof BytecodeRecorderImpl.ReturnedProxy) {
- routeBuildItemBuildProducer.produce(new RouteBuildItem(
- new RuntimeDevConsoleRoute(groupAndArtifact.getKey(), groupAndArtifact.getValue(), i.getPath(),
- i.getMethod()),
- i.getHandler()));
+ routeBuildItemBuildProducer.produce(nonApplicationRootPathBuildItem.routeBuilder()
+ .routeFunction(
+ "dev/" + groupAndArtifact.getKey() + "." + groupAndArtifact.getValue() + "/" + i.getPath(),
+ new RuntimeDevConsoleRoute(i.getMethod()))
+ .handler(i.getHandler())
+ .build());
} else {
router.route(HttpMethod.valueOf(i.getMethod()),
"/" + groupAndArtifact.getKey() + "." + groupAndArtifact.getValue() + "/" + i.getPath())
@@ -310,40 +301,32 @@ public void setupActions(List routes,
DevConsoleManager.registerHandler(new DevConsoleHttpHandler());
//must be last so the above routes have precedence
- routeBuildItemBuildProducer.produce(new RouteBuildItem.Builder()
- .route("/dev/*")
+ routeBuildItemBuildProducer.produce(nonApplicationRootPathBuildItem.routeBuilder()
+ .route("dev/*")
.handler(new DevConsoleFilter())
- .nonApplicationRoute(false)
.build());
- routeBuildItemBuildProducer.produce(new RouteBuildItem.Builder()
- .route("/dev")
+ routeBuildItemBuildProducer.produce(nonApplicationRootPathBuildItem.routeBuilder()
+ .route("dev")
+ .displayOnNotFoundPage("Dev UI")
.handler(new RedirectHandler())
- .nonApplicationRoute(false)
.build());
}
- @BuildStep(onlyIf = IsDevelopment.class)
- public void setupActions(BuildProducer displayableEndpoints,
- NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) {
- displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(
- nonApplicationRootPathBuildItem.adjustPath("/dev/"), "Dev UI"));
- }
-
@BuildStep(onlyIf = IsDevelopment.class)
@Record(ExecutionTime.RUNTIME_INIT)
public void deployStaticResources(DevConsoleRecorder recorder, CurateOutcomeBuildItem curateOutcomeBuildItem,
LaunchModeBuildItem launchMode, ShutdownContextBuildItem shutdownContext,
- BuildProducer routeBuildItemBuildProducer) throws IOException {
+ BuildProducer routeBuildItemBuildProducer,
+ NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) throws IOException {
AppArtifact devConsoleResourcesArtifact = WebJarUtil.getAppArtifact(curateOutcomeBuildItem, "io.quarkus",
"quarkus-vertx-http-deployment");
Path devConsoleStaticResourcesDeploymentPath = WebJarUtil.copyResourcesForDevOrTest(curateOutcomeBuildItem, launchMode,
devConsoleResourcesArtifact, STATIC_RESOURCES_PATH);
- routeBuildItemBuildProducer.produce(new RouteBuildItem.Builder()
- .route("/dev/resources/*")
+ routeBuildItemBuildProducer.produce(nonApplicationRootPathBuildItem.routeBuilder()
+ .route("dev/resources/*")
.handler(recorder.devConsoleHandler(devConsoleStaticResourcesDeploymentPath.toString(), shutdownContext))
- .nonApplicationRoute(false)
.build());
}
@@ -458,7 +441,8 @@ public Optional getVariant() {
});
}
- private void collectTemplates(BuildProducer devTemplatePaths) {
+ @BuildStep
+ void collectTemplates(BuildProducer devTemplatePaths) {
try {
ClassLoader classLoader = DevConsoleProcessor.class.getClassLoader();
Enumeration devTemplateURLs = classLoader.getResources("/dev-templates");
diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/NonApplicationAndRootPathTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/NonApplicationAndRootPathTest.java
index e68a1b5b7af1e..b73a2bf0d103c 100644
--- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/NonApplicationAndRootPathTest.java
+++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/NonApplicationAndRootPathTest.java
@@ -16,6 +16,7 @@
import io.quarkus.builder.BuildContext;
import io.quarkus.builder.BuildStep;
import io.quarkus.test.QuarkusUnitTest;
+import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
import io.quarkus.vertx.http.deployment.RouteBuildItem;
import io.restassured.RestAssured;
import io.vertx.core.Handler;
@@ -39,14 +40,16 @@ public void accept(BuildChainBuilder builder) {
builder.addBuildStep(new BuildStep() {
@Override
public void execute(BuildContext context) {
- context.produce(new RouteBuildItem.Builder()
- .route("/non-app")
+ NonApplicationRootPathBuildItem buildItem = context.consume(NonApplicationRootPathBuildItem.class);
+ context.produce(buildItem.routeBuilder()
+ .route("non-app-relative")
.handler(new MyHandler())
.blockingRoute()
- .nonApplicationRoute()
+ .requiresLegacyRedirect()
.build());
}
}).produces(RouteBuildItem.class)
+ .consumes(NonApplicationRootPathBuildItem.class)
.build();
}
};
@@ -64,13 +67,13 @@ public void handle(RoutingContext routingContext) {
@Test
public void testNonApplicationEndpointOnRootPathWithRedirect() {
// Note RestAssured knows the path prefix is /api
- RestAssured.given().get("/non-app").then().statusCode(200).body(Matchers.equalTo("/api/q/non-app"));
+ RestAssured.given().get("/non-app-relative").then().statusCode(200).body(Matchers.equalTo("/api/q/non-app-relative"));
}
@Test
public void testNonApplicationEndpointDirect() {
// Note RestAssured knows the path prefix is /api
- RestAssured.given().get("/q/non-app").then().statusCode(200).body(Matchers.equalTo("/api/q/non-app"));
+ RestAssured.given().get("/q/non-app-relative").then().statusCode(200).body(Matchers.equalTo("/api/q/non-app-relative"));
}
@Singleton
diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/NonApplicationEscapeTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/NonApplicationEscapeTest.java
new file mode 100644
index 0000000000000..2fdac9dcbcce7
--- /dev/null
+++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/NonApplicationEscapeTest.java
@@ -0,0 +1,113 @@
+package io.quarkus.vertx.http;
+
+import java.net.URL;
+import java.time.Duration;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+import javax.enterprise.event.Observes;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.awaitility.Awaitility;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.builder.BuildChainBuilder;
+import io.quarkus.builder.BuildContext;
+import io.quarkus.builder.BuildStep;
+import io.quarkus.test.QuarkusUnitTest;
+import io.quarkus.test.common.http.TestHTTPResource;
+import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
+import io.quarkus.vertx.http.deployment.RouteBuildItem;
+import io.vertx.core.Handler;
+import io.vertx.core.Vertx;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.ext.web.RoutingContext;
+import io.vertx.ext.web.client.HttpResponse;
+import io.vertx.ext.web.client.WebClient;
+import io.vertx.ext.web.client.predicate.ResponsePredicate;
+
+public class NonApplicationEscapeTest {
+ private static final String APP_PROPS = "" +
+ "quarkus.http.root-path=/api\n" +
+ "quarkus.http.non-application-root-path=${quarkus.http.root-path}\n";
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
+ .addAsResource(new StringAsset(APP_PROPS), "application.properties")
+ .addClasses(MyObserver.class))
+ .addBuildChainCustomizer(buildCustomizer());
+
+ static Consumer buildCustomizer() {
+ return new Consumer() {
+ @Override
+ public void accept(BuildChainBuilder builder) {
+ builder.addBuildStep(new BuildStep() {
+ @Override
+ public void execute(BuildContext context) {
+ NonApplicationRootPathBuildItem buildItem = context.consume(NonApplicationRootPathBuildItem.class);
+ context.produce(buildItem.routeBuilder()
+ .route("/non-app-absolute")
+ .handler(new MyHandler())
+ .blockingRoute()
+ .requiresLegacyRedirect()
+ .build());
+ }
+ }).produces(RouteBuildItem.class)
+ .consumes(NonApplicationRootPathBuildItem.class)
+ .build();
+ }
+ };
+ }
+
+ @TestHTTPResource("/")
+ URL uri;
+
+ @Inject
+ Vertx vertx;
+
+ public static class MyHandler implements Handler {
+ @Override
+ public void handle(RoutingContext routingContext) {
+ routingContext.response()
+ .setStatusCode(200)
+ .end(routingContext.request().path());
+ }
+ }
+
+ @Test
+ public void testNonApplicationEndpointEscaped() {
+ AtomicReference result = new AtomicReference<>();
+
+ WebClient.create(vertx)
+ .get(uri.getPort(), uri.getHost(), "/non-app-absolute")
+ .expect(ResponsePredicate.SC_OK)
+ .send(ar -> {
+ if (ar.succeeded()) {
+ HttpResponse response = ar.result();
+ result.set(response.bodyAsString());
+ } else {
+ result.set(ar.cause().getMessage());
+ }
+ });
+
+ Awaitility.await().atMost(Duration.ofMinutes(2)).until(() -> result.get() != null);
+
+ Assertions.assertEquals("/non-app-absolute", result.get());
+ }
+
+ @Singleton
+ static class MyObserver {
+
+ void test(@Observes String event) {
+ //Do Nothing
+ }
+
+ }
+}
diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/NonApplicationRootPathSiblingTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/NonApplicationRootPathSiblingTest.java
new file mode 100644
index 0000000000000..3cbd535370d15
--- /dev/null
+++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/NonApplicationRootPathSiblingTest.java
@@ -0,0 +1,82 @@
+package io.quarkus.vertx.http;
+
+import java.util.function.Consumer;
+
+import javax.enterprise.event.Observes;
+import javax.inject.Singleton;
+
+import org.hamcrest.Matchers;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.builder.BuildChainBuilder;
+import io.quarkus.builder.BuildContext;
+import io.quarkus.builder.BuildStep;
+import io.quarkus.test.QuarkusUnitTest;
+import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
+import io.quarkus.vertx.http.deployment.RouteBuildItem;
+import io.restassured.RestAssured;
+import io.vertx.core.Handler;
+import io.vertx.ext.web.RoutingContext;
+
+public class NonApplicationRootPathSiblingTest {
+ private static final String APP_PROPS = "" +
+ "quarkus.http.root-path=/api\n" +
+ "quarkus.http.non-application-root-path=${quarkus.http.root-path}";
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
+ .addAsResource(new StringAsset(APP_PROPS), "application.properties")
+ .addClasses(MyObserver.class))
+ .addBuildChainCustomizer(buildCustomizer());
+
+ static Consumer buildCustomizer() {
+ return new Consumer() {
+ @Override
+ public void accept(BuildChainBuilder builder) {
+ builder.addBuildStep(new BuildStep() {
+ @Override
+ public void execute(BuildContext context) {
+ NonApplicationRootPathBuildItem buildItem = context.consume(NonApplicationRootPathBuildItem.class);
+ context.produce(buildItem.routeBuilder()
+ .route("non-app-relative")
+ .handler(new MyHandler())
+ .blockingRoute()
+ .requiresLegacyRedirect()
+ .build());
+ }
+ }).produces(RouteBuildItem.class)
+ .consumes(NonApplicationRootPathBuildItem.class)
+ .build();
+ }
+ };
+ }
+
+ public static class MyHandler implements Handler {
+ @Override
+ public void handle(RoutingContext routingContext) {
+ routingContext.response()
+ .setStatusCode(200)
+ .end(routingContext.request().path());
+ }
+ }
+
+ @Test
+ public void testNonApplicationEndpoint() {
+ // Note RestAssured knows the path prefix is /api
+ RestAssured.given().get("/non-app-relative").then().statusCode(200).body(Matchers.equalTo("/api/non-app-relative"));
+ }
+
+ @Singleton
+ static class MyObserver {
+
+ void test(@Observes String event) {
+ //Do Nothing
+ }
+
+ }
+}
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
new file mode 100644
index 0000000000000..456675376bf6b
--- /dev/null
+++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/deployment/HttpRootPathBuildItemTest.java
@@ -0,0 +1,32 @@
+package io.quarkus.vertx.http.deployment;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class HttpRootPathBuildItemTest {
+ @Test
+ void testResolvePathWithSlash() {
+ HttpRootPathBuildItem buildItem = new HttpRootPathBuildItem("/");
+
+ Assertions.assertEquals("/", buildItem.resolvePath(""));
+ Assertions.assertEquals("/foo", buildItem.resolvePath("foo"));
+ Assertions.assertEquals("/foo", buildItem.resolvePath("/foo"));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> buildItem.resolvePath("../foo"));
+ }
+
+ @Test
+ void testResolvePathWithSlashApp() {
+ HttpRootPathBuildItem buildItem = new HttpRootPathBuildItem("/app");
+
+ Assertions.assertEquals("/app/", buildItem.resolvePath(""));
+ Assertions.assertEquals("/app/foo", buildItem.resolvePath("foo"));
+ Assertions.assertEquals("/foo", buildItem.resolvePath("/foo"));
+ }
+
+ @Test
+ void testResolveResolvedPath() {
+ HttpRootPathBuildItem buildItem = new HttpRootPathBuildItem("/app");
+ Assertions.assertEquals("/app", buildItem.resolvePath("/app"));
+ Assertions.assertEquals("/app/foo", buildItem.resolvePath("/app/foo"));
+ }
+}
diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/deployment/NonApplicationRootPathBuildItemTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/deployment/NonApplicationRootPathBuildItemTest.java
new file mode 100644
index 0000000000000..4844ba60a7ba8
--- /dev/null
+++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/deployment/NonApplicationRootPathBuildItemTest.java
@@ -0,0 +1,101 @@
+package io.quarkus.vertx.http.deployment;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class NonApplicationRootPathBuildItemTest {
+ @Test
+ void testResolvePathWithSlashRelativeQ() {
+ NonApplicationRootPathBuildItem buildItem = new NonApplicationRootPathBuildItem("/", "q");
+ Assertions.assertTrue(buildItem.isDedicatedRouterRequired());
+ Assertions.assertTrue(buildItem.attachedToMainRouter);
+ Assertions.assertEquals("/q/", buildItem.getVertxRouterPath());
+ Assertions.assertEquals("/", buildItem.httpRootPath.getPath());
+ Assertions.assertEquals("/q/", buildItem.nonApplicationRootPath.getPath());
+ Assertions.assertNotEquals(buildItem.httpRootPath, buildItem.nonApplicationRootPath);
+
+ Assertions.assertEquals("/q/foo", buildItem.resolvePath("foo"));
+ Assertions.assertEquals("/q/foo/sub/path", buildItem.resolvePath("foo/sub/path"));
+ Assertions.assertEquals("/foo", buildItem.resolvePath("/foo"));
+ Assertions.assertEquals("/foo/sub/path", buildItem.resolvePath("/foo/sub/path"));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> buildItem.resolvePath("../foo"));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> buildItem.resolvePath(""));
+ }
+
+ @Test
+ void testResolvePathWithSlashAbsoluteQ() {
+ NonApplicationRootPathBuildItem buildItem = new NonApplicationRootPathBuildItem("/", "/q");
+ Assertions.assertTrue(buildItem.isDedicatedRouterRequired());
+ Assertions.assertTrue(buildItem.attachedToMainRouter);
+ Assertions.assertEquals("/q/", buildItem.getVertxRouterPath());
+ Assertions.assertEquals("/", buildItem.httpRootPath.getPath());
+ Assertions.assertEquals("/q/", buildItem.nonApplicationRootPath.getPath());
+ Assertions.assertNotEquals(buildItem.httpRootPath, buildItem.nonApplicationRootPath);
+
+ Assertions.assertEquals("/q/foo", buildItem.resolvePath("foo"));
+ Assertions.assertEquals("/foo", buildItem.resolvePath("/foo"));
+ }
+
+ @Test
+ void testResolvePathWithSlashAppWithRelativeQ() {
+ NonApplicationRootPathBuildItem buildItem = new NonApplicationRootPathBuildItem("/app", "q");
+ Assertions.assertTrue(buildItem.isDedicatedRouterRequired());
+ Assertions.assertTrue(buildItem.attachedToMainRouter);
+ Assertions.assertEquals("/q/", buildItem.getVertxRouterPath());
+ Assertions.assertEquals("/app/", buildItem.httpRootPath.getPath());
+ Assertions.assertEquals("/app/q/", buildItem.nonApplicationRootPath.getPath());
+ Assertions.assertNotEquals(buildItem.httpRootPath, buildItem.nonApplicationRootPath);
+
+ Assertions.assertEquals("/app/q/foo", buildItem.resolvePath("foo"));
+ Assertions.assertEquals("/foo", buildItem.resolvePath("/foo"));
+ }
+
+ @Test
+ void testResolvePathWithSlashAppWithAbsoluteQ() {
+ NonApplicationRootPathBuildItem buildItem = new NonApplicationRootPathBuildItem("/app", "/q");
+ Assertions.assertTrue(buildItem.isDedicatedRouterRequired());
+ Assertions.assertFalse(buildItem.attachedToMainRouter);
+ Assertions.assertEquals("/q/", buildItem.getVertxRouterPath());
+ Assertions.assertEquals("/app/", buildItem.httpRootPath.getPath());
+ Assertions.assertEquals("/q/", buildItem.nonApplicationRootPath.getPath());
+ Assertions.assertNotEquals(buildItem.httpRootPath, buildItem.nonApplicationRootPath);
+
+ Assertions.assertEquals("/q/foo", buildItem.resolvePath("foo"));
+ Assertions.assertEquals("/foo", buildItem.resolvePath("/foo"));
+ }
+
+ @Test
+ void testResolvePathWithSlashEmpty() {
+ NonApplicationRootPathBuildItem buildItem = new NonApplicationRootPathBuildItem("/", "");
+ Assertions.assertFalse(buildItem.isDedicatedRouterRequired());
+ Assertions.assertTrue(buildItem.attachedToMainRouter);
+ Assertions.assertEquals("/", buildItem.getVertxRouterPath());
+ Assertions.assertEquals("/", buildItem.httpRootPath.getPath());
+ Assertions.assertEquals("/", buildItem.nonApplicationRootPath.getPath());
+ Assertions.assertEquals(buildItem.httpRootPath, buildItem.nonApplicationRootPath);
+
+ Assertions.assertEquals("/foo", buildItem.resolvePath("foo"));
+ Assertions.assertEquals("/foo", buildItem.resolvePath("/foo"));
+ }
+
+ @Test
+ void testResolvePathWithSlashWithSlash() {
+ NonApplicationRootPathBuildItem buildItem = new NonApplicationRootPathBuildItem("/", "/");
+ Assertions.assertFalse(buildItem.isDedicatedRouterRequired());
+ Assertions.assertTrue(buildItem.attachedToMainRouter);
+ Assertions.assertEquals("/", buildItem.getVertxRouterPath());
+ Assertions.assertEquals("/", buildItem.httpRootPath.getPath());
+ Assertions.assertEquals("/", buildItem.nonApplicationRootPath.getPath());
+ Assertions.assertEquals(buildItem.httpRootPath, buildItem.nonApplicationRootPath);
+
+ Assertions.assertEquals("/foo", buildItem.resolvePath("foo"));
+ Assertions.assertEquals("/foo", buildItem.resolvePath("/foo"));
+ }
+
+ @Test
+ void testResolvePathWithSlashWithSlashQWithWildcards() {
+ NonApplicationRootPathBuildItem buildItem = new NonApplicationRootPathBuildItem("/", "/q");
+ Assertions.assertEquals("/q/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/BasicRoute.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/BasicRoute.java
index 181e5cbd8d761..064145e894d45 100644
--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/BasicRoute.java
+++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/BasicRoute.java
@@ -1,5 +1,6 @@
package io.quarkus.vertx.http.runtime;
+import java.util.function.Consumer;
import java.util.function.Function;
import io.vertx.ext.web.Route;
@@ -11,6 +12,8 @@ public class BasicRoute implements Function {
private Integer order;
+ private Consumer customizer;
+
public BasicRoute(String path) {
this(path, null);
}
@@ -20,6 +23,12 @@ public BasicRoute(String path, Integer order) {
this.order = order;
}
+ public BasicRoute(String path, Integer order, Consumer customizer) {
+ this.path = path;
+ this.order = order;
+ this.customizer = customizer;
+ }
+
public BasicRoute() {
}
@@ -39,12 +48,24 @@ public void setOrder(Integer order) {
this.order = order;
}
+ public Consumer getCustomizer() {
+ return customizer;
+ }
+
+ public BasicRoute setCustomizer(Consumer customizer) {
+ this.customizer = customizer;
+ return this;
+ }
+
@Override
public Route apply(Router router) {
Route route = router.route(path);
if (order != null) {
route.order(order);
}
+ if (customizer != null) {
+ customizer.accept(route);
+ }
return route;
}
}
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 fc97336e4b4de..172a24288d0ad 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
@@ -31,29 +31,41 @@ public class HttpBuildTimeConfig {
public boolean virtual;
/**
- * The HTTP root path for non application endpoints. Various endpoints such as metrics, health,
- * and open api are deployed under this path.
- * Setting the value to "/" disables the separate non application root,
- * resulting in all non application endpoints being served from "/" along with the application.
+ * A common root path for non-application endpoints. Various extension-provided endpoints such as metrics, health,
+ * and openapi are deployed under this path by default.
+ *
+ * * Relative path (Default, `q`) ->
+ * Non-application endpoints will be served from
+ * `${quarkus.http.root-path}/${quarkus.http.non-application-root-path}`.
+ * * Absolute path (`/q`) ->
+ * 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")
+ @ConfigItem(defaultValue = "q")
public String nonApplicationRootPath;
/**
- * Whether to redirect non application endpoints from previous location off the root to the
- * new non application root path.
- * Enabled by default.
+ * Provide redirect endpoints for extension-provided endpoints existing prior to Quarkus 1.11.
+ * This will trigger HTTP 301 Redirects for the following:
+ *
+ * * `/graphql-ui`
+ * * `/health`
+ * * `/health-ui`
+ * * `/metrics`
+ * * `/openapi`
+ * * `/swagger-ui`
+ *
+ * Default is `true` for Quarkus 1.11.x to facilitate transition to name-spaced URIs using
+ * `${quarkus.http.non-application-root-path}`.
+ *
+ * Quarkus 1.13 will change the default to `false`,
+ * and the config item will be removed in Quarkus 2.0.
+ *
+ * @asciidoclet
*/
@ConfigItem(defaultValue = "true")
public boolean redirectToNonApplicationRootPath;
-
- public String adjustPath(String path) {
- if (!path.startsWith("/")) {
- throw new IllegalArgumentException("Path must start with /");
- }
- if (rootPath.equals("/")) {
- return path;
- }
- return rootPath + path;
- }
}
diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java
index cbe271a5158e3..91c2ee7eb7a72 100644
--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java
+++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java
@@ -259,13 +259,13 @@ public void mountFrameworkRouter(RuntimeValue mainRouter, RuntimeValue defaultRouteHandler,
List filterList, Supplier vertx,
- LiveReloadConfig liveReloadConfig,
- RuntimeValue runtimeValue, String rootPath, LaunchMode launchMode, boolean requireBodyHandler,
+ LiveReloadConfig liveReloadConfig, Optional> mainRouterRuntimeValue,
+ RuntimeValue httpRouterRuntimeValue, String rootPath, LaunchMode launchMode, boolean requireBodyHandler,
Handler bodyHandler, HttpConfiguration httpConfiguration,
GracefulShutdownFilter gracefulShutdownFilter, ShutdownConfig shutdownConfig,
Executor executor) {
// install the default route at the end
- Router router = runtimeValue.getValue();
+ Router httpRouteRouter = httpRouterRuntimeValue.getValue();
//allow the router to be modified programmatically
Event