From 59490485e0f301c87f655b37092d9dfdeb0b58ad Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Tue, 9 Mar 2021 13:07:58 +1100 Subject: [PATCH 01/11] Disable DevUI for remote dev mode (cherry picked from commit 2dd118d2b1ec98886fb2c4e86fca1f0f0e93e9b0) --- .../runner/bootstrap/AugmentActionImpl.java | 1 + .../devmode/console/DevConsoleProcessor.java | 14 ++++++++++++-- .../java/io/quarkus/maven/it/RemoteDevMojoIT.java | 3 +++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java b/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java index b73e99541793b..46513f13edab0 100644 --- a/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java +++ b/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java @@ -278,6 +278,7 @@ private BuildResult runAugment(boolean firstRun, Set changedResources, } builder.setLaunchMode(launchMode); + builder.setDevModeType(devModeType); builder.setRebuild(quarkusBootstrap.isRebuild()); if (firstRun) { builder.setLiveReloadState( 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 a71a945b9ad91..88fe746024276 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 @@ -55,6 +55,7 @@ import io.quarkus.deployment.util.ArtifactInfoUtil; import io.quarkus.deployment.util.WebJarUtil; import io.quarkus.dev.console.DevConsoleManager; +import io.quarkus.dev.spi.DevModeType; import io.quarkus.devconsole.spi.DevConsoleRouteBuildItem; import io.quarkus.devconsole.spi.DevConsoleRuntimeTemplateInfoBuildItem; import io.quarkus.devconsole.spi.DevConsoleTemplateInfoBuildItem; @@ -271,7 +272,11 @@ public void setupActions(List routes, LogStreamRecorder recorder, CurateOutcomeBuildItem curateOutcomeBuildItem, HistoryHandlerBuildItem historyHandlerBuildItem, - NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) { + NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, + LaunchModeBuildItem launchModeBuildItem) { + if (launchModeBuildItem.getDevModeType().orElse(null) != DevModeType.LOCAL) { + return; + } initializeVirtual(); newRouter(buildEngine(devTemplatePaths), nonApplicationRootPathBuildItem); @@ -317,7 +322,12 @@ public void setupActions(List routes, public void deployStaticResources(DevConsoleRecorder recorder, CurateOutcomeBuildItem curateOutcomeBuildItem, LaunchModeBuildItem launchMode, ShutdownContextBuildItem shutdownContext, BuildProducer routeBuildItemBuildProducer, - NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) throws IOException { + NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, + LaunchModeBuildItem launchModeBuildItem) throws IOException { + + if (launchModeBuildItem.getDevModeType().orElse(DevModeType.LOCAL) != DevModeType.LOCAL) { + return; + } AppArtifact devConsoleResourcesArtifact = WebJarUtil.getAppArtifact(curateOutcomeBuildItem, "io.quarkus", "quarkus-vertx-http-deployment"); diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/RemoteDevMojoIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/RemoteDevMojoIT.java index b6849b389d12e..429aa61401664 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/RemoteDevMojoIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/RemoteDevMojoIT.java @@ -54,6 +54,9 @@ public void testThatTheApplicationIsReloadedOnJavaChange() await() .pollDelay(1, TimeUnit.SECONDS) .atMost(1, TimeUnit.MINUTES).until(() -> DevModeTestUtils.getHttpResponse("/app/hello").contains("carambar")); + + //also verify that the dev ui console is disabled + DevModeTestUtils.getHttpResponse("/q/dev", 404, 10, TimeUnit.SECONDS); } @Test From a6d7c2feb6bcd8f5eff2f8f62c3e6991776256d5 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Mon, 8 Mar 2021 22:26:39 +0200 Subject: [PATCH 02/11] Added descriptions to Health OpenAPI Filter responses Signed-off-by:Phillip Kruger (cherry picked from commit 8d3352559105da08d62100598be211ccb7bed668) --- .../smallrye/health/deployment/HealthOpenAPIFilter.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/HealthOpenAPIFilter.java b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/HealthOpenAPIFilter.java index de652acb15373..96051e54fbc61 100644 --- a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/HealthOpenAPIFilter.java +++ b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/HealthOpenAPIFilter.java @@ -125,14 +125,15 @@ private Operation createReadinessOperation() { private APIResponses createAPIResponses() { APIResponses responses = new APIResponsesImpl(); - responses.addAPIResponse("200", createAPIResponse()); - responses.addAPIResponse("503", createAPIResponse()); - responses.addAPIResponse("500", createAPIResponse()); + responses.addAPIResponse("200", createAPIResponse("OK")); + responses.addAPIResponse("503", createAPIResponse("Service Unavailable")); + responses.addAPIResponse("500", createAPIResponse("Internal Server Error")); return responses; } - private APIResponse createAPIResponse() { + private APIResponse createAPIResponse(String description) { APIResponse response = new APIResponseImpl(); + response.setDescription(description); response.setContent(createContent()); return response; } From 3c79467042664affb19ab834825138afc821f93a Mon Sep 17 00:00:00 2001 From: markusdlugi Date: Tue, 9 Mar 2021 09:05:13 +0100 Subject: [PATCH 03/11] Fix ClassRoutingHandler not using sortedOriginalMediaTypes in else block (cherry picked from commit b624e78ffa6be16334c34e8df4a1fd97a6c61d38) --- .../resteasy/reactive/server/handlers/ClassRoutingHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ClassRoutingHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ClassRoutingHandler.java index 8fa8621fac167..8ba2f4100f5ea 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ClassRoutingHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ClassRoutingHandler.java @@ -137,7 +137,7 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti } else { acceptsMediaTypes = Collections.singletonList(toMediaType(accepts)); } - if (MediaTypeHelper.getFirstMatch(Arrays.asList(target.value.getProduces().getSortedMediaTypes()), + if (MediaTypeHelper.getFirstMatch(Arrays.asList(target.value.getProduces().getSortedOriginalMediaTypes()), acceptsMediaTypes) == null) { throw new NotAcceptableException(); } From eb342f454bdc8de276e39184a9fb9b4c34faca57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Marti=C5=A1ka?= Date: Tue, 9 Mar 2021 12:05:34 +0100 Subject: [PATCH 04/11] Fix GraphQL queries returning Collection.class in native mode (cherry picked from commit 9397de4fc049edd113ed1a3282c96aa9e13e35f6) --- .../graphql/deployment/SmallRyeGraphQLProcessor.java | 1 + .../io/quarkus/it/smallrye/graphql/GreetingResource.java | 8 ++++++++ .../quarkus/it/smallrye/graphql/GreetingResourceTest.java | 3 +++ 3 files changed, 12 insertions(+) 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 50a616dc55d9b..177613933e365 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 @@ -268,6 +268,7 @@ private Class[] getGraphQLJavaClasses() { classes.add(graphql.schema.GraphQLSchema.class); classes.add(graphql.schema.GraphQLTypeReference.class); classes.add(List.class); + classes.add(Collection.class); return classes.toArray(new Class[] {}); } diff --git a/integration-tests/smallrye-graphql/src/main/java/io/quarkus/it/smallrye/graphql/GreetingResource.java b/integration-tests/smallrye-graphql/src/main/java/io/quarkus/it/smallrye/graphql/GreetingResource.java index d5dcd75dab4bd..e3708cb98a71f 100644 --- a/integration-tests/smallrye-graphql/src/main/java/io/quarkus/it/smallrye/graphql/GreetingResource.java +++ b/integration-tests/smallrye-graphql/src/main/java/io/quarkus/it/smallrye/graphql/GreetingResource.java @@ -2,6 +2,7 @@ import java.time.LocalTime; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.eclipse.microprofile.config.inject.ConfigProperty; @@ -36,6 +37,13 @@ public Greetings load(Greetings greetings) { public List buildInOptions(@Source Greeting greeting) { List options = new ArrayList<>(); options.add(new Hello()); + return options; + } + + @Name("options2") + // make sure that returning Collection.class does not break native mode + public Collection buildInOptionsCollection(@Source Greeting greeting) { + List options = new ArrayList<>(); options.add(new Morning()); return options; } diff --git a/integration-tests/smallrye-graphql/src/test/java/io/quarkus/it/smallrye/graphql/GreetingResourceTest.java b/integration-tests/smallrye-graphql/src/test/java/io/quarkus/it/smallrye/graphql/GreetingResourceTest.java index fb313728f51c5..9ff8af8a1d9e4 100644 --- a/integration-tests/smallrye-graphql/src/test/java/io/quarkus/it/smallrye/graphql/GreetingResourceTest.java +++ b/integration-tests/smallrye-graphql/src/test/java/io/quarkus/it/smallrye/graphql/GreetingResourceTest.java @@ -22,6 +22,9 @@ void testEndpoint() { " options{\n" + " message\n" + " }\n" + + " options2{\n" + + " message\n" + + " }\n" + " }\n" + " farewell:farewell{\n" + " type\n" + From b3a9d8da774d3c4db2ec6b095fc086ac26a979f0 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 9 Mar 2021 18:43:58 +0200 Subject: [PATCH 05/11] Fix potential race condition with Multipart upload in RESTEasy Reactive As spotted by @cescoffier, the old endHandler was not called when the files were written to the FS, but when the last piece of data of the file was read. Co-authored-by: Clement Escoffier (cherry picked from commit f7ede57123ac5adfb26fb55dd642d38cc1c59e24) --- .../server/runtime/MultipartFormHandler.java | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/MultipartFormHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/MultipartFormHandler.java index 324367cca4bd6..249330862a9a7 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/MultipartFormHandler.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/MultipartFormHandler.java @@ -132,22 +132,16 @@ public void handle(HttpServerFileUpload upload) { uploadCount.incrementAndGet(); String uploadedFileName = new File(MultipartFormVertxHandler.this.uploadsDirectory, UUID.randomUUID().toString()).getPath(); - upload.streamToFileSystem(uploadedFileName); + upload.exceptionHandler(new UploadExceptionHandler(rrContext)); + upload.streamToFileSystem(uploadedFileName).exceptionHandler(new UploadExceptionHandler(rrContext)) + .endHandler(new Handler() { + @Override + public void handle(Void event) { + uploadEnded(); + } + }); FileUploadImpl fileUpload = new FileUploadImpl(uploadedFileName, upload); fileUploads.add(fileUpload); - upload.exceptionHandler(new Handler() { - @Override - public void handle(Throwable t) { - MultipartFormVertxHandler.this.deleteFileUploads(); - rrContext.resume(new WebApplicationException(t, Response.Status.INTERNAL_SERVER_ERROR)); - } - }); - upload.endHandler(new Handler() { - @Override - public void handle(Void event) { - uploadEnded(); - } - }); } }); } @@ -221,5 +215,19 @@ private void deleteFileUploads() { } } } + + private class UploadExceptionHandler implements Handler { + private final ResteasyReactiveRequestContext rrContext; + + public UploadExceptionHandler(ResteasyReactiveRequestContext rrContext) { + this.rrContext = rrContext; + } + + @Override + public void handle(Throwable t) { + MultipartFormVertxHandler.this.deleteFileUploads(); + rrContext.resume(new WebApplicationException(t, Response.Status.INTERNAL_SERVER_ERROR)); + } + } } } From 88f713b23472174a32cdd0c95a04b061df57b395 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 8 Mar 2021 22:19:29 +0200 Subject: [PATCH 06/11] Fix race condition on reading input in RESTEasy Reactive Fixes: #15479 (cherry picked from commit 1fd5305a7a82c370d7706bb8b608e1f8b16f0b9f) --- .../server/vertx/VertxResteasyReactiveRequestContext.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxResteasyReactiveRequestContext.java b/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxResteasyReactiveRequestContext.java index 7e3a1ab4f98ff..04a838b51adbb 100644 --- a/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxResteasyReactiveRequestContext.java +++ b/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxResteasyReactiveRequestContext.java @@ -217,6 +217,7 @@ public ServerHttpResponse resumeRequestInput() { @Override public ServerHttpResponse setReadListener(ReadCallback callback) { + request.pause(); if (continueState == ContinueState.REQUIRED) { continueState = ContinueState.SENT; response.writeContinue(); @@ -233,6 +234,7 @@ public void handle(Void event) { callback.done(); } }); + request.resume(); return this; } From 0d69430c9c581c6fdbf94f67802f739059ab79d5 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Wed, 10 Mar 2021 09:33:23 +1100 Subject: [PATCH 07/11] Fix Polyglot with fast-jar (cherry picked from commit e00468771898232b098778bd82c77dd43ba8f627) --- core/runtime/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/core/runtime/pom.xml b/core/runtime/pom.xml index 983f97e025e12..5042dbaf65f15 100644 --- a/core/runtime/pom.xml +++ b/core/runtime/pom.xml @@ -151,6 +151,7 @@ org.jacoco:org.jacoco.agent:runtime + org.graalvm.sdk:graal-sdk io.quarkus:quarkus-bootstrap-runner io.quarkus:quarkus-development-mode-spi From 358868b74fec8d7fcaa0bf7b7a7f47c9fdea307d Mon Sep 17 00:00:00 2001 From: Guillaume Le Floch Date: Tue, 9 Mar 2021 22:31:43 +0100 Subject: [PATCH 08/11] Look for RedisClient in all modules (cherry picked from commit ac273f991cda30ab6d5bb98ff0537c70efa40da3) --- .../redis/client/deployment/RedisClientProcessor.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/RedisClientProcessor.java b/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/RedisClientProcessor.java index 8be65bfaef81c..319d51f966a1c 100644 --- a/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/RedisClientProcessor.java +++ b/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/RedisClientProcessor.java @@ -16,13 +16,13 @@ import org.jboss.jandex.IndexView; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; import io.quarkus.deployment.Feature; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; -import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; @@ -80,13 +80,13 @@ RuntimeInitializedClassBuildItem initializeBulkTypeDuringRuntime() { @BuildStep @Record(ExecutionTime.RUNTIME_INIT) - public void produceRedisClient(RedisClientRecorder recorder, ApplicationArchivesBuildItem applicationArchives, + public void produceRedisClient(RedisClientRecorder recorder, BeanArchiveIndexBuildItem indexBuildItem, BuildProducer syntheticBeans, VertxBuildItem vertxBuildItem) { Set clientNames = new HashSet<>(); clientNames.add(RedisClientUtil.DEFAULT_CLIENT); - IndexView indexView = applicationArchives.getRootArchive().getIndex(); + IndexView indexView = indexBuildItem.getIndex(); Collection clientAnnotations = indexView.getAnnotations(REDIS_CLIENT_ANNOTATION); for (AnnotationInstance annotation : clientAnnotations) { clientNames.add(annotation.value().asString()); From 7e8ffc75eb2563be67e107b12502ea6ff95af746 Mon Sep 17 00:00:00 2001 From: Erin Schnabel Date: Mon, 8 Mar 2021 17:30:12 -0500 Subject: [PATCH 09/11] Fix DevConsole paths;HttpRootPathBuildItem.Builder * Expose configured HTTP paths to the DevUI * Add Builder to HttpRootPathBuildItem to help create RouteBuildItems using resolved paths * Create devConsoleAppend attribute for resolving static dev console resources Co-authored-by: Stuart Douglas (cherry picked from commit 307696aadb2661e411cd67797c987f2b73800483) --- .../deployment/util/UriNormalizationUtil.java | 8 ++ docs/src/main/asciidoc/dev-ui.adoc | 69 ++++++++-- .../export/PrometheusRegistryProcessor.java | 2 +- .../deployment/SmallRyeGraphQLConfig.java | 5 +- .../deployment/SmallRyeGraphQLProcessor.java | 12 +- .../deployment/SmallRyeGraphQLUIConfig.java | 1 + .../resources/dev-templates/embedded.html | 4 +- .../deployment/SmallRyeHealthConfig.java | 5 + .../deployment/SmallRyeHealthProcessor.java | 45 +++---- .../deployment/SmallRyeHealthUIConfig.java | 1 + .../resources/dev-templates/embedded.html | 4 +- .../runtime/SmallRyeHealthStaticHandler.java | 1 - .../deployment/SmallRyeMetricsProcessor.java | 2 +- .../deployment/SmallRyeOpenApiProcessor.java | 1 + .../resources/dev-templates/embedded.html | 4 +- .../swaggerui/deployment/SwaggerUiConfig.java | 1 + .../deployment/SwaggerUiProcessor.java | 10 +- .../deployment/VertxGraphqlProcessor.java | 11 +- .../deployment/HttpRootPathBuildItem.java | 126 ++++++++++++++++++ .../NonApplicationRootPathBuildItem.java | 58 ++++---- .../vertx/http/deployment/RouteBuildItem.java | 71 +++++++++- .../http/deployment/VertxHttpProcessor.java | 3 +- ...FoundPageDisplayableEndpointBuildItem.java | 4 +- .../devmode/console/ConfiguredPathInfo.java | 37 +++++ .../devmode/console/DevConsole.java | 20 +-- .../devmode/console/DevConsoleProcessor.java | 76 ++++++++--- .../dev-templates/logmanagerNav.html | 2 +- .../main/resources/dev-templates/main.html | 22 +-- 28 files changed, 464 insertions(+), 141 deletions(-) create mode 100644 extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ConfiguredPathInfo.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/UriNormalizationUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/util/UriNormalizationUtil.java index bb6b858232c5a..3a629bf968b16 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/util/UriNormalizationUtil.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/util/UriNormalizationUtil.java @@ -104,4 +104,12 @@ public static URI normalizeWithBase(URI base, String segment, boolean trailingSl URI resolvedUri = base.resolve(segmentUri); return resolvedUri; } + + public static String relativize(String rootPath, String leafPath) { + if (leafPath.startsWith(rootPath)) { + return leafPath.substring(rootPath.length()); + } + + return null; + } } diff --git a/docs/src/main/asciidoc/dev-ui.adoc b/docs/src/main/asciidoc/dev-ui.adoc index a788f9823678a..2f5cdc9940e1a 100644 --- a/docs/src/main/asciidoc/dev-ui.adoc +++ b/docs/src/main/asciidoc/dev-ui.adoc @@ -10,7 +10,7 @@ include::./attributes.adoc[] This guide covers the Quarkus Dev UI for link:building-my-first-extension[extension authors]. Quarkus now ships with a new experimental Dev UI, which is available in dev mode (when you start -quarkus with `mvn quarkus:dev`) at http://localhost:8080/q/dev[/q/dev] and will show you something like +quarkus with `mvn quarkus:dev`) at http://localhost:8080/q/dev[/q/dev] by default. It will show you something like this: image::dev-ui-overview.png[alt=Dev UI overview,role="center"] @@ -46,16 +46,56 @@ two links with some styling and icons: [source,html] ---- - - + + OpenAPI - - +
+
+ Swagger UI ---- TIP: We use the Font Awesome Free icon set. +Note how the paths are specified: `{config:http-path('quarkus.smallrye-openapi.path')}`. This is a special +directive that the quarkus dev console understands: it will replace that value with the resolved route +named 'quarkus.smallrye-openapi.path'. + +The corresponding non-application endpoint is declared using `.routeConfigKey` to associate the route with a name: + +[source,java] +---- + nonApplicationRootPathBuildItem.routeBuilder() + .route(openApiConfig.path) // <1> + .routeConfigKey("quarkus.smallrye-openapi.path") // <2> + ... + .build(); +---- +<1> The configured path is resolved into a valid route. +<2> The resolved route path is then associated with the key `quarkus.smallrye-openapi.path`. + +== Path considerations + +Paths are tricky business. Keep the following in mind: + +* Assume your UI will be nested under the dev endpoint. Do not provide a way to customize this without a strong reason. +* Never construct your own absolute paths. Adding a suffix to a known, normalized and resolved path is fine. + +Configured paths, like the `dev` endpoint used by the console or the SmallRye OpenAPI path shown in the example above, +need to be properly resolved against both `quarkus.http.root-path` and `quarkus.http.non-application-root-path`. +Use `NonApplicationRootPathBuildItem` or `HttpRootPathBuildItem` to construct endpoint routes and identify resolved +path values that can then be used in templates. + +The `{devRootAppend}` variable can also be used in templates to construct URLs for static dev console resources, for example: + +[source,html] +---- +Quarkus +---- + +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. + == Template and styling support Both the `embedded.html` files and any full page you add in `/dev-templates` will be interpreted by @@ -75,6 +115,9 @@ A `config:property(name)` expression can be used to output the config value for The property name can be either a string literal or obtained dynamically by another expression. For example `{config:property('quarkus.lambda.handler')}` and `{config:property(foo.propertyName)}`. +Reminder: do not use this to retrieve raw configured path values. As shown above, use `{config:http-path(...)}` with +a known route configuration key when working with resource paths. + == Adding full pages To add full pages for your Dev UI extension such as this one: @@ -137,17 +180,19 @@ link:building-my-first-extension#description-of-a-quarkus-extension[`deployment` == Linking to your full-page templates -Every full-page template lives under the `/q/dev/{groupId}.{artifactId}/` URI (for example -`/q/dev/io.quarkus.quarkus-cache/`), so if you want to link -to them from your `embedded.html` file you can use the `urlbase` template parameter to point to them: +Full-page templates for extensions live under a pre-defined `{devRootAppend}/{groupId}.{artifactId}/` directory +that is referenced using the `urlbase` template parameter. Using configuration defaults, that would resolve to +`/q/dev/io.quarkus.quarkus-cache/`, as an example. -[source,java] +Use the `{urlbase}` template parameter to reference this folder in `embedded.html`: + +[source,html] ---- // <1> Caches {info:cacheInfos.size()} ---- -<1> Use the `urlbase` template parameter to point to where your full-page templates are located +<1> Use the `urlbase` template parameter to reference full-page templates for your extension == Passing information to your templates @@ -266,6 +311,7 @@ This can be done by adding another link:building-my-first-extension#deploying-th declare the action in your extension's link:building-my-first-extension#description-of-a-quarkus-extension[`deployment`] module: + [source,java] ---- package io.quarkus.cache.deployment.devconsole; @@ -290,11 +336,13 @@ public class DevConsoleProcessor { <1> Mark the recorder as optional, so it will only be invoked when in dev mode <2> Declare a `POST {urlbase}/caches` route handled by the given handler + Note: you can see <>. Now all you have to do is implement the recorder in your extension's link:building-my-first-extension#description-of-a-quarkus-extension[`runtime module`]: + [source,java] ---- package io.quarkus.cache.runtime.devconsole; @@ -338,6 +386,7 @@ public class CacheDevConsoleRecorder { <3> Don't forget to add a message for the user to let them know everything went fine <4> You can also add error messages + NOTE: Flash messages are handled by the `main` DEV template and will result in nice notifications for your users: 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 7c512dd094d44..19d0bd12d5e04 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 @@ -65,7 +65,7 @@ void createPrometheusRoute(BuildProducer routes, .routeFunction(pConfig.path, recorder.route()) .handler(recorder.getHandler()) .requiresLegacyRedirect() - .displayOnNotFoundPage("Metrics", pConfig.path) + .displayOnNotFoundPage("Metrics") .build()); // Match paths that begin with the deployment path diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLConfig.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLConfig.java index ec2612bc1e34a..481963c940d1c 100644 --- a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLConfig.java +++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLConfig.java @@ -11,9 +11,10 @@ public class SmallRyeGraphQLConfig { /** - * The rootPath under which queries will be served. Default to /graphql + * The rootPath under which queries will be served. Default to graphql + * By default, this value will be resolved as a path relative to `${quarkus.http.root-path}`. */ - @ConfigItem(defaultValue = "/graphql") + @ConfigItem(defaultValue = "graphql") String rootPath; /** 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 177613933e365..4eb7c535760b4 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 @@ -81,7 +81,7 @@ */ public class SmallRyeGraphQLProcessor { private static final Logger LOG = Logger.getLogger(SmallRyeGraphQLProcessor.class); - private static final String SCHEMA_PATH = "/schema.graphql"; + private static final String SCHEMA_PATH = "schema.graphql"; // For Service integration private static final String SERVICE_NOT_AVAILABLE_WARNING = "The %s property is true, but the %s extension is not present. SmallRye GraphQL %s will be disabled."; @@ -181,14 +181,15 @@ void buildExecutionService( @BuildStep void buildSchemaEndpoint( BuildProducer routeProducer, + HttpRootPathBuildItem httpRootPathBuildItem, SmallRyeGraphQLInitializedBuildItem graphQLInitializedBuildItem, SmallRyeGraphQLRecorder recorder, SmallRyeGraphQLConfig graphQLConfig) { Handler schemaHandler = recorder.schemaHandler(graphQLInitializedBuildItem.getInitialized()); - routeProducer.produce(new RouteBuildItem.Builder() - .route(graphQLConfig.rootPath + SCHEMA_PATH) + routeProducer.produce(httpRootPathBuildItem.routeBuilder() + .nestedRoute(graphQLConfig.rootPath, SCHEMA_PATH) .handler(schemaHandler) .displayOnNotFoundPage("MicroProfile GraphQL Schema") .blockingRoute() @@ -201,6 +202,7 @@ void buildSchemaEndpoint( @Consume(BeanContainerBuildItem.class) void buildExecutionEndpoint( BuildProducer routeProducer, + HttpRootPathBuildItem httpRootPathBuildItem, SmallRyeGraphQLInitializedBuildItem graphQLInitializedBuildItem, SmallRyeGraphQLRecorder recorder, ShutdownContextBuildItem shutdownContext, @@ -225,9 +227,10 @@ void buildExecutionEndpoint( Handler executionHandler = recorder.executionHandler(graphQLInitializedBuildItem.getInitialized(), allowGet); - routeProducer.produce(new RouteBuildItem.Builder() + routeProducer.produce(httpRootPathBuildItem.routeBuilder() .routeFunction(graphQLConfig.rootPath, recorder.routeFunction(bodyHandlerBuildItem.getHandler())) .handler(executionHandler) + .routeConfigKey("quarkus.smallrye-graphql.root-path") .displayOnNotFoundPage("MicroProfile GraphQL Endpoint") .blockingRoute() .build()); @@ -528,6 +531,7 @@ void registerGraphQLUiHandler( smallRyeGraphQLBuildItem.getGraphqlUiPath(), runtimeConfig); routeProducer.produce(nonApplicationRootPathBuildItem.routeBuilder() .route(graphQLConfig.ui.rootPath) + .routeConfigKey("quarkus.smallrye-graphql.ui.root-path") .displayOnNotFoundPage("MicroProfile GraphQL UI") .handler(handler) .requiresLegacyRedirect() 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 19cee4b8415a6..f90f7aef7f132 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 @@ -9,6 +9,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. + * By default, this URL will be resolved as a path relative to `${quarkus.http.non-application-root-path}`. */ @ConfigItem(defaultValue = "graphql-ui") String rootPath; diff --git a/extensions/smallrye-graphql/deployment/src/main/resources/dev-templates/embedded.html b/extensions/smallrye-graphql/deployment/src/main/resources/dev-templates/embedded.html index 60015ff1e292d..772d905b9902b 100644 --- a/extensions/smallrye-graphql/deployment/src/main/resources/dev-templates/embedded.html +++ b/extensions/smallrye-graphql/deployment/src/main/resources/dev-templates/embedded.html @@ -1,7 +1,7 @@ - + GraphQL Schema
- + GraphQL UI 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 9a80dcc1bed0a..7cb60dfabf3fd 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 @@ -9,30 +9,35 @@ public class SmallRyeHealthConfig { /** * Root path for health-checking endpoints. + * By default, this value will be resolved as a path relative to `${quarkus.http.non-application-root-path}`. */ @ConfigItem(defaultValue = "health") String rootPath; /** * The relative path of the liveness health-checking endpoint. + * By default, this value will be resolved as a path relative to `${quarkus.smallrye-health.rootPath}`. */ @ConfigItem(defaultValue = "live") String livenessPath; /** * The relative path of the readiness health-checking endpoint. + * By default, this value will be resolved as a path relative to `${quarkus.smallrye-health.rootPath}`. */ @ConfigItem(defaultValue = "ready") String readinessPath; /** * The relative path of the health group endpoint. + * By default, this value will be resolved as a path relative to `${quarkus.smallrye-health.rootPath}`. */ @ConfigItem(defaultValue = "group") String groupPath; /** * The relative path of the wellness health-checking endpoint. + * By default, this value will be resolved as a path relative to `${quarkus.smallrye-health.rootPath}`. */ @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 571a7a321c72b..4bfcd89cd8f42 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 @@ -69,11 +69,8 @@ import io.quarkus.smallrye.health.runtime.SmallRyeReadinessHandler; import io.quarkus.smallrye.health.runtime.SmallRyeWellnessHandler; import io.quarkus.smallrye.openapi.deployment.spi.AddToOpenAPIDefinitionBuildItem; -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.HttpBuildTimeConfig; import io.smallrye.health.SmallRyeHealthReporter; import io.smallrye.health.api.HealthGroup; import io.smallrye.health.api.HealthGroups; @@ -97,7 +94,8 @@ class SmallRyeHealthProcessor { private static final String HEALTH_UI_WEBJAR_ARTIFACT_ID = "smallrye-health-ui"; private static final String HEALTH_UI_WEBJAR_PREFIX = "META-INF/resources/health-ui/"; private static final String HEALTH_UI_FINAL_DESTINATION = "META-INF/health-ui-files"; - private static final String FILE_TO_UPDATE = "healthui.js"; + private static final String JS_FILE_TO_UPDATE = "healthui.js"; + private static final String INDEX_FILE_TO_UPDATE = "index.html"; // Branding files to monitor for changes private static final String BRANDING_DIR = "META-INF/branding/"; @@ -150,11 +148,7 @@ void healthCheck(BuildProducer buildItemBuildProducer, void build(SmallRyeHealthRecorder recorder, BuildProducer feature, BuildProducer additionalBean, - BuildProducer beanDefiningAnnotation, - BuildProducer displayableEndpoints, - NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, - LaunchModeBuildItem launchMode, - SmallRyeHealthConfig healthConfig) + BuildProducer beanDefiningAnnotation) throws IOException, ClassNotFoundException { feature.produce(new FeatureBuildItem(Feature.SMALLRYE_HEALTH)); @@ -207,6 +201,7 @@ public void defineHealthRoutes(BuildProducer routes, // Register the health handler routes.produce(nonApplicationRootPathBuildItem.routeBuilder() .route(healthConfig.rootPath) + .routeConfigKey("quarkus.smallrye-health.root-path") .handler(new SmallRyeHealthHandler()) .requiresLegacyRedirect() .displayOnNotFoundPage() @@ -278,16 +273,17 @@ public void defineHealthRoutes(BuildProducer routes, @BuildStep(onlyIf = OpenAPIIncluded.class) public void includeInOpenAPIEndpoint(BuildProducer openAPIProducer, NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, - HttpRootPathBuildItem httpRootPath, Capabilities capabilities, SmallRyeHealthConfig healthConfig) { // Add to OpenAPI if OpenAPI is available if (capabilities.isPresent(Capability.SMALLRYE_OPENAPI)) { String healthRootPath = nonApplicationRootPathBuildItem.resolvePath(healthConfig.rootPath); + HealthOpenAPIFilter filter = new HealthOpenAPIFilter(healthRootPath, nonApplicationRootPathBuildItem.resolveNestedPath(healthRootPath, healthConfig.livenessPath), nonApplicationRootPathBuildItem.resolveNestedPath(healthRootPath, healthConfig.readinessPath)); + openAPIProducer.produce(new AddToOpenAPIDefinitionBuildItem(filter)); } } @@ -316,17 +312,17 @@ private void warnIfJaxRsPathUsed(IndexView index, DotName healthAnnotation) { } @BuildStep - public void kubernetes(HttpBuildTimeConfig httpConfig, NonApplicationRootPathBuildItem frameworkRootPath, + public void kubernetes(NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, SmallRyeHealthConfig healthConfig, BuildProducer livenessPathItemProducer, BuildProducer readinessPathItemProducer) { livenessPathItemProducer.produce( new KubernetesHealthLivenessPathBuildItem( - frameworkRootPath.resolveNestedPath(healthConfig.rootPath, healthConfig.livenessPath))); + nonApplicationRootPathBuildItem.resolveNestedPath(healthConfig.rootPath, healthConfig.livenessPath))); readinessPathItemProducer.produce( new KubernetesHealthReadinessPathBuildItem( - frameworkRootPath.resolveNestedPath(healthConfig.rootPath, healthConfig.readinessPath))); + nonApplicationRootPathBuildItem.resolveNestedPath(healthConfig.rootPath, healthConfig.readinessPath))); } @BuildStep @@ -386,7 +382,6 @@ void registerUiExtension( BuildProducer generatedResourceProducer, BuildProducer nativeImageResourceProducer, BuildProducer smallRyeHealthBuildProducer, - HttpRootPathBuildItem httpRootPath, NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, SmallRyeHealthConfig healthConfig, CurateOutcomeBuildItem curateOutcomeBuildItem, @@ -400,9 +395,8 @@ 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.resolvePath(nonApplicationRootPathBuildItem.resolvePath(healthConfig.rootPath)); - String healthUiPath = httpRootPath - .resolvePath(nonApplicationRootPathBuildItem.resolvePath(healthConfig.ui.rootPath)); + String healthPath = nonApplicationRootPathBuildItem.resolvePath(healthConfig.rootPath); + String healthUiPath = nonApplicationRootPathBuildItem.resolvePath(healthConfig.ui.rootPath); AppArtifact artifact = WebJarUtil.getAppArtifact(curateOutcomeBuildItem, HEALTH_UI_WEBJAR_GROUP_ID, HEALTH_UI_WEBJAR_ARTIFACT_ID); @@ -410,7 +404,8 @@ void registerUiExtension( if (launchMode.getLaunchMode().isDevOrTest()) { Path tempPath = WebJarUtil.copyResourcesForDevOrTest(curateOutcomeBuildItem, launchMode, artifact, HEALTH_UI_WEBJAR_PREFIX); - updateApiUrl(tempPath.resolve(FILE_TO_UPDATE), healthPath); + updateApiUrl(tempPath.resolve(JS_FILE_TO_UPDATE), healthPath); + updateApiUrl(tempPath.resolve(INDEX_FILE_TO_UPDATE), healthPath); smallRyeHealthBuildProducer .produce(new SmallRyeHealthBuildItem(tempPath.toAbsolutePath().toString(), healthUiPath)); @@ -428,7 +423,7 @@ void registerUiExtension( String fileName = file.getKey(); byte[] content = file.getValue(); - if (fileName.endsWith(FILE_TO_UPDATE)) { + if (fileName.endsWith(JS_FILE_TO_UPDATE) || fileName.endsWith(INDEX_FILE_TO_UPDATE)) { content = updateApiUrl(new String(content, StandardCharsets.UTF_8), healthPath) .getBytes(StandardCharsets.UTF_8); } @@ -457,8 +452,10 @@ void registerHealthUiHandler( if (shouldInclude(launchMode, healthConfig)) { Handler handler = recorder.uiHandler(smallRyeHealthBuildItem.getHealthUiFinalDestination(), smallRyeHealthBuildItem.getHealthUiPath(), runtimeConfig); + routeProducer.produce(nonApplicationRootPathBuildItem.routeBuilder() .route(healthConfig.ui.rootPath) + .routeConfigKey("quarkus.smallrye-health.ui.root-path") .displayOnNotFoundPage("Health UI") .requiresLegacyRedirect() .handler(handler) @@ -471,16 +468,18 @@ void registerHealthUiHandler( } } - private void updateApiUrl(Path healthUiJs, String healthPath) throws IOException { - String content = new String(Files.readAllBytes(healthUiJs), StandardCharsets.UTF_8); + private void updateApiUrl(Path fileToUpdate, String healthPath) throws IOException { + String content = new String(Files.readAllBytes(fileToUpdate), StandardCharsets.UTF_8); String result = updateApiUrl(content, healthPath); if (result != null) { - Files.write(healthUiJs, result.getBytes(StandardCharsets.UTF_8)); + Files.write(fileToUpdate, result.getBytes(StandardCharsets.UTF_8)); } } + // Replace health URL in static files public String updateApiUrl(String original, String healthPath) { - return original.replace("url = \"/health\";", "url = \"" + healthPath + "\";"); + return original.replace("url = \"/health\";", "url = \"" + healthPath + "\";") + .replace("placeholder=\"/health\"", "placeholder=\"" + healthPath + "\""); } private static boolean shouldInclude(LaunchModeBuildItem launchMode, SmallRyeHealthConfig healthConfig) { 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 f85e119bbce3c..be3861e301175 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 @@ -8,6 +8,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. + * By default, this value will be resolved as a path relative to `${quarkus.http.non-application-root-path}`. */ @ConfigItem(defaultValue = "health-ui") String rootPath; diff --git a/extensions/smallrye-health/deployment/src/main/resources/dev-templates/embedded.html b/extensions/smallrye-health/deployment/src/main/resources/dev-templates/embedded.html index 6dff17579d7d8..36f058723ff00 100644 --- a/extensions/smallrye-health/deployment/src/main/resources/dev-templates/embedded.html +++ b/extensions/smallrye-health/deployment/src/main/resources/dev-templates/embedded.html @@ -1,7 +1,7 @@ - + Health
- + Health UI diff --git a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthStaticHandler.java b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthStaticHandler.java index 70a5a53d0e072..266d2cb33dcd5 100644 --- a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthStaticHandler.java +++ b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthStaticHandler.java @@ -44,7 +44,6 @@ public void handle(RoutingContext event) { .setDefaultContentEncoding("UTF-8"); if (event.normalisedPath().length() == healthUiPath.length()) { - event.response().setStatusCode(302); event.response().headers().set(HttpHeaders.LOCATION, healthUiPath + "/"); event.response().end(); 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 5bfbadd59f33d..310ae17e4d580 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 @@ -163,12 +163,12 @@ void createRoute(BuildProducer routes, .route(metrics.path + (metrics.path.endsWith("/") ? "*" : "/*")) .handler(recorder.handler(frameworkRoot.resolvePath(metrics.path))) .requiresLegacyRedirect() - .displayOnNotFoundPage("Metrics", metrics.path) .blockingRoute() .build()); routes.produce(frameworkRoot.routeBuilder() .route(metrics.path) .handler(recorder.handler(frameworkRoot.resolvePath(metrics.path))) + .displayOnNotFoundPage("Metrics") .requiresLegacyRedirect() .blockingRoute() .build()); 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 0749a676da798..4ad0ef4b84b2c 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 @@ -172,6 +172,7 @@ RouteBuildItem handler(LaunchModeBuildItem launch, Handler handler = recorder.handler(openApiRuntimeConfig); return nonApplicationRootPathBuildItem.routeBuilder() .route(openApiConfig.path) + .routeConfigKey("quarkus.smallrye-openapi.path") .handler(handler) .displayOnNotFoundPage("Open API Schema document") .requiresLegacyRedirect() diff --git a/extensions/smallrye-openapi/deployment/src/main/resources/dev-templates/embedded.html b/extensions/smallrye-openapi/deployment/src/main/resources/dev-templates/embedded.html index 8f548cc42dd17..301c0af54e22a 100644 --- a/extensions/smallrye-openapi/deployment/src/main/resources/dev-templates/embedded.html +++ b/extensions/smallrye-openapi/deployment/src/main/resources/dev-templates/embedded.html @@ -1,7 +1,7 @@ - + OpenAPI
- + Swagger UI 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 ccb86c99987ab..7f50ce0c10e54 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 @@ -18,6 +18,7 @@ public class SwaggerUiConfig { * The path where Swagger UI is available. *

* The value `/` is not allowed as it blocks the application from serving anything else. + * By default, this value will be resolved as a path relative to `${quarkus.http.non-application-root-path}`. */ @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 40c828dd7f9e3..c45e43a5675d6 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 @@ -27,10 +27,8 @@ import io.quarkus.smallrye.openapi.common.deployment.SmallRyeOpenApiConfig; import io.quarkus.swaggerui.runtime.SwaggerUiRecorder; import io.quarkus.swaggerui.runtime.SwaggerUiRuntimeConfig; -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.smallrye.openapi.ui.IndexHtmlCreator; import io.smallrye.openapi.ui.Option; import io.smallrye.openapi.ui.ThemeHref; @@ -79,12 +77,10 @@ public void getSwaggerUiFinalDestination( BuildProducer nativeImageResourceBuildItemBuildProducer, BuildProducer swaggerUiBuildProducer, NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, - BuildProducer displayableEndpoints, CurateOutcomeBuildItem curateOutcomeBuildItem, LaunchModeBuildItem launchMode, SwaggerUiConfig swaggerUiConfig, SmallRyeOpenApiConfig openapi, - HttpRootPathBuildItem httpRootPathBuildItem, LiveReloadBuildItem liveReloadBuildItem) throws Exception { if (shouldInclude(launchMode, swaggerUiConfig)) { @@ -93,9 +89,8 @@ 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.resolvePath(nonApplicationRootPathBuildItem.resolvePath(openapi.path)); - String swaggerUiPath = httpRootPathBuildItem - .resolvePath(nonApplicationRootPathBuildItem.resolvePath(swaggerUiConfig.path)); + String openApiPath = nonApplicationRootPathBuildItem.resolvePath(openapi.path); + String swaggerUiPath = nonApplicationRootPathBuildItem.resolvePath(swaggerUiConfig.path); AppArtifact artifact = WebJarUtil.getAppArtifact(curateOutcomeBuildItem, SWAGGER_UI_WEBJAR_GROUP_ID, SWAGGER_UI_WEBJAR_ARTIFACT_ID); @@ -156,6 +151,7 @@ public void registerSwaggerUiHandler(SwaggerUiRecorder recorder, routes.produce(nonApplicationRootPathBuildItem.routeBuilder() .route(swaggerUiConfig.path) .displayOnNotFoundPage("Open API UI") + .routeConfigKey("quarkus.swagger-ui.path") .handler(handler) .requiresLegacyRedirect() .build()); 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 08cce8ddce3ec..8c00ac69b8bb4 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 @@ -68,12 +68,17 @@ void registerVertxGraphqlUI(VertxGraphqlRecorder recorder, } Handler handler = recorder.handler(); - routes.produce(nonApplicationRootPathBuildItem.routeBuilder().route(path).handler(handler) + routes.produce(nonApplicationRootPathBuildItem.routeBuilder() + .route(path) + .handler(handler) .requiresLegacyRedirect() - .displayOnNotFoundPage("GraphQL UI", path + "/") + .displayOnNotFoundPage("GraphQL UI") .build()); routes.produce( - nonApplicationRootPathBuildItem.routeBuilder().route(path + "/*").handler(handler).requiresLegacyRedirect() + nonApplicationRootPathBuildItem.routeBuilder() + .route(path + "/*") + .handler(handler) + .requiresLegacyRedirect() .build()); nativeResourcesProducer.produce(new NativeImageResourceDirectoryBuildItem("io/vertx/ext/web/handler/graphiql")); } 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 a1bf1775c2867..28b897640ce6d 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,9 +1,18 @@ 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.deployment.devmode.console.ConfiguredPathInfo; +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 HttpRootPathBuildItem extends SimpleBuildItem { @@ -65,4 +74,121 @@ public String adjustPath(String path) { public String resolvePath(String path) { return UriNormalizationUtil.normalizeWithBase(rootPath, path, false).getPath(); } + + public HttpRootPathBuildItem.Builder routeBuilder() { + return new HttpRootPathBuildItem.Builder(this); + } + + public static class Builder extends RouteBuildItem.Builder { + private final HttpRootPathBuildItem buildItem; + private RouteBuildItem.RouteType routeType = RouteBuildItem.RouteType.APPLICATION_ROUTE; + private String path; + + Builder(HttpRootPathBuildItem buildItem) { + this.buildItem = buildItem; + } + + @Override + public Builder routeFunction(Function routeFunction) { + throw new RuntimeException( + "This method is not supported using this builder. Use #routeFunction(String, Consumer)"); + } + + public Builder routeFunction(String route, Consumer routeFunction) { + route = super.absolutePath = buildItem.resolvePath(route); + + if (route.startsWith(buildItem.getRootPath())) { + // relative to http root (leading slash for vert.x route) + this.path = "/" + UriNormalizationUtil.relativize(buildItem.getRootPath(), 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; + } + + @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() { + super.displayOnNotFoundPage(); + return this; + } + + @Override + public Builder displayOnNotFoundPage(String notFoundPageTitle) { + super.displayOnNotFoundPage(notFoundPageTitle); + return this; + } + + @Override + public Builder displayOnNotFoundPage(String notFoundPageTitle, String notFoundPagePath) { + super.displayOnNotFoundPage(notFoundPageTitle, notFoundPagePath); + return this; + } + + @Override + public Builder routeConfigKey(String attributeName) { + super.routeConfigKey(attributeName); + return this; + } + + @Override + public RouteBuildItem build() { + return new RouteBuildItem(this, routeType, false); + } + + @Override + protected ConfiguredPathInfo getRouteConfigInfo() { + return super.getRouteConfigInfo(); + } + + @Override + protected NotFoundPageDisplayableEndpointBuildItem getNotFoundEndpoint() { + return super.getNotFoundEndpoint(); + } + } } 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 c6426ae14ebb5..e16242c878c30 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 @@ -7,6 +7,7 @@ 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.deployment.devmode.console.ConfiguredPathInfo; import io.quarkus.vertx.http.runtime.HandlerType; import io.vertx.core.Handler; import io.vertx.ext.web.Route; @@ -69,20 +70,12 @@ public boolean isAttachedToMainRouter() { */ String getVertxRouterPath() { if (attachedToMainRouter) { - return "/" + relativize(httpRootPath.getPath(), nonApplicationRootPath.getPath()); + return "/" + UriNormalizationUtil.relativize(httpRootPath.getPath(), nonApplicationRootPath.getPath()); } else { return getNonApplicationRootPath(); } } - String relativize(String rootPath, String leafPath) { - if (leafPath.startsWith(rootPath)) { - return leafPath.substring(rootPath.length()); - } - - return null; - } - public String getNormalizedHttpRootPath() { return httpRootPath.getPath(); } @@ -189,7 +182,6 @@ public static class Builder extends RouteBuildItem.Builder { private boolean requiresLegacyRedirect = false; private RouteBuildItem.RouteType routeType = RouteBuildItem.RouteType.FRAMEWORK_ROUTE; private String path; - private String absolute; Builder(NonApplicationRootPathBuildItem buildItem) { this.buildItem = buildItem; @@ -197,23 +189,23 @@ public static class Builder extends RouteBuildItem.Builder { @Override public Builder routeFunction(Function routeFunction) { - throw new RuntimeException("This method is not supported for non-application routes"); + throw new RuntimeException( + "This method is not supported using this builder. Use #routeFunction(String, Consumer)"); } public Builder routeFunction(String route, Consumer routeFunction) { - String temp = route; - route = absolute = buildItem.resolvePath(route); + route = super.absolutePath = buildItem.resolvePath(route); boolean isFrameworkRoute = buildItem.dedicatedRouterRequired && route.startsWith(buildItem.getNonApplicationRootPath()); if (isFrameworkRoute) { - // relative non-application root - this.path = "/" + buildItem.relativize(buildItem.getNonApplicationRootPath(), route); + // relative non-application root (leading slash for vert.x) + this.path = "/" + UriNormalizationUtil.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); + // relative to http root (leading slash for vert.x route) + this.path = "/" + UriNormalizationUtil.relativize(buildItem.httpRootPath.getPath(), route); this.routeType = RouteBuildItem.RouteType.APPLICATION_ROUTE; } else if (route.startsWith("/")) { // absolute path @@ -277,43 +269,45 @@ public Builder failureRoute() { @Override public Builder displayOnNotFoundPage() { - this.displayOnNotFoundPage = true; + super.displayOnNotFoundPage(); return this; } @Override public Builder displayOnNotFoundPage(String notFoundPageTitle) { - this.displayOnNotFoundPage = true; - this.notFoundPageTitle = notFoundPageTitle; + super.displayOnNotFoundPage(notFoundPageTitle); return this; } @Override public Builder displayOnNotFoundPage(String notFoundPageTitle, String notFoundPagePath) { - this.displayOnNotFoundPage = true; - this.notFoundPageTitle = notFoundPageTitle; - this.notFoundPagePath = notFoundPagePath; + super.displayOnNotFoundPage(notFoundPageTitle, notFoundPagePath); + return this; + } + + @Override + public Builder routeConfigKey(String attributeName) { + super.routeConfigKey(attributeName); return this; } + @Override public RouteBuildItem build() { // If path is same as absolute, we don't enable legacy redirect - if (requiresLegacyRedirect && path.equals(absolute)) { + if (requiresLegacyRedirect && path.equals(super.absolutePath)) { requiresLegacyRedirect = false; } return new RouteBuildItem(this, routeType, requiresLegacyRedirect); } + @Override + protected ConfiguredPathInfo getRouteConfigInfo() { + return super.getRouteConfigInfo(); + } + @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 super.getNotFoundEndpoint(); } } } 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 2f732f16c473e..0c89744514a19 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 @@ -5,6 +5,7 @@ import io.quarkus.builder.item.MultiBuildItem; import io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem; +import io.quarkus.vertx.http.deployment.devmode.console.ConfiguredPathInfo; import io.quarkus.vertx.http.runtime.BasicRoute; import io.quarkus.vertx.http.runtime.HandlerType; import io.vertx.core.Handler; @@ -24,6 +25,7 @@ public static Builder builder() { private final RouteType routeType; private final boolean requiresLegacyRedirect; private final NotFoundPageDisplayableEndpointBuildItem notFoundPageDisplayableEndpoint; + private final ConfiguredPathInfo devConsoleResolvedPathBuildItem; /** * @deprecated Use the Builder instead. @@ -36,6 +38,7 @@ public RouteBuildItem(Function routeFunction, Handler handler) { this.routeType = routeType; this.requiresLegacyRedirect = requiresLegacyRedirect; this.notFoundPageDisplayableEndpoint = builder.getNotFoundEndpoint(); + this.devConsoleResolvedPathBuildItem = builder.getRouteConfigInfo(); } public Handler getHandler() { @@ -119,6 +123,10 @@ public NotFoundPageDisplayableEndpointBuildItem getNotFoundPageDisplayableEndpoi return notFoundPageDisplayableEndpoint; } + public ConfiguredPathInfo getDevConsoleResolvedPath() { + return devConsoleResolvedPathBuildItem; + } + public enum RouteType { FRAMEWORK_ROUTE, APPLICATION_ROUTE, @@ -126,7 +134,7 @@ public enum RouteType { } /** - * NonApplicationRootPathBuildItem.Builder extends this. + * HttpRootPathBuildItem.Builder and NonApplicationRootPathBuildItem.Builder extend this. * Please verify the extended builders behavior when changing this one. */ public static class Builder { @@ -136,12 +144,27 @@ public static class Builder { protected boolean displayOnNotFoundPage; protected String notFoundPageTitle; protected String notFoundPagePath; + protected String routePath; + protected String routeConfigKey; + protected String absolutePath; + + /** + * Use HttpRootPathBuildItem and NonApplicationRootPathBuildItem to + * ensure paths are constructed/normalized correctly + * + * @deprecated + * @see HttpRootPathBuildItem#routeBuilder() + * @see NonApplicationRootPathBuildItem#routeBuilder() + */ + @Deprecated + Builder() { + } /** * {@link #routeFunction(String, Consumer)} should be used instead * * @param routeFunction - * @return + * @see #routeFunction(String, Consumer) */ @Deprecated public Builder routeFunction(Function routeFunction) { @@ -149,15 +172,25 @@ public Builder routeFunction(Function routeFunction) { return this; } + /** + * @param path A normalized path (e.g. use HttpRootPathBuildItem to construct/resolve the path value) defining + * the route. This path this is also used on the "Not Found" page in dev mode. + * @param routeFunction a Consumer of Route + */ public Builder routeFunction(String path, Consumer routeFunction) { this.routeFunction = new BasicRoute(path, null, routeFunction); - this.notFoundPagePath = path; + this.notFoundPagePath = this.routePath = path; return this; } + /** + * @param route A normalized path used to define a basic route + * (e.g. use HttpRootPathBuildItem to construct/resolve the path value). This path this is also + * used on the "Not Found" page in dev mode. + */ public Builder route(String route) { this.routeFunction = new BasicRoute(route); - this.notFoundPagePath = route; + this.notFoundPagePath = this.routePath = route; return this; } @@ -192,6 +225,12 @@ public Builder displayOnNotFoundPage(String notFoundPageTitle) { return this; } + /** + * @deprecated Specify the path as part of defining the route + * @see #route(String) + * @see #routeFunction(String, Consumer) + */ + @Deprecated public Builder displayOnNotFoundPage(String notFoundPageTitle, String notFoundPagePath) { this.displayOnNotFoundPage = true; this.notFoundPageTitle = notFoundPageTitle; @@ -199,10 +238,29 @@ public Builder displayOnNotFoundPage(String notFoundPageTitle, String notFoundPa return this; } + public Builder routeConfigKey(String attributeName) { + this.routeConfigKey = attributeName; + return this; + } + public RouteBuildItem build() { return new RouteBuildItem(this, RouteType.APPLICATION_ROUTE, false); } + protected ConfiguredPathInfo getRouteConfigInfo() { + if (routeConfigKey == null) { + return null; + } + if (routePath == null) { + throw new RuntimeException("Cannot discover value of " + routeConfigKey + + " as no explicit path was specified and a route function is in use"); + } + if (absolutePath != null) { + return new ConfiguredPathInfo(routeConfigKey, absolutePath, true); + } + return new ConfiguredPathInfo(routeConfigKey, routePath, false); + } + protected NotFoundPageDisplayableEndpointBuildItem getNotFoundEndpoint() { if (!displayOnNotFoundPage) { return null; @@ -211,7 +269,10 @@ protected NotFoundPageDisplayableEndpointBuildItem getNotFoundEndpoint() { 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); + if (absolutePath != null) { + return new NotFoundPageDisplayableEndpointBuildItem(absolutePath, notFoundPageTitle, true); + } + return new NotFoundPageDisplayableEndpointBuildItem(notFoundPagePath, notFoundPageTitle, false); } } } 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 dd39d2a01b3a2..347fa4fc38dca 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 @@ -120,14 +120,13 @@ public KubernetesPortBuildItem kubernetes() { } @BuildStep - void notRoundRoutes( + void notFoundRoutes( List routes, BuildProducer notFound) { for (RouteBuildItem i : routes) { if (i.getNotFoundPageDisplayableEndpoint() != null) { notFound.produce(i.getNotFoundPageDisplayableEndpoint()); } - } } 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 a0d88468946d6..638db01bcf52a 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 @@ -13,10 +13,10 @@ public NotFoundPageDisplayableEndpointBuildItem(String endpoint, String descript this(endpoint, description, false); } - public NotFoundPageDisplayableEndpointBuildItem(String endpoint, String description, boolean absolutePath) { + public NotFoundPageDisplayableEndpointBuildItem(String endpoint, String description, boolean isAbsolutePath) { this.endpoint = endpoint; this.description = description; - this.absolutePath = absolutePath; + this.absolutePath = isAbsolutePath; } public NotFoundPageDisplayableEndpointBuildItem(String endpoint) { diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ConfiguredPathInfo.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ConfiguredPathInfo.java new file mode 100644 index 0000000000000..a522178d15ab9 --- /dev/null +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ConfiguredPathInfo.java @@ -0,0 +1,37 @@ +package io.quarkus.vertx.http.deployment.devmode.console; + +import io.quarkus.runtime.TemplateHtmlBuilder; +import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; +import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem; + +public class ConfiguredPathInfo { + private final String name; + private final String endpointPath; + private final boolean absolutePath; + + public ConfiguredPathInfo(String name, String endpointPath, boolean isAbsolutePath) { + this.name = name; + this.endpointPath = endpointPath; + this.absolutePath = isAbsolutePath; + } + + public String getName() { + return name; + } + + public String getEndpointPath(HttpRootPathBuildItem httpRoot) { + if (absolutePath) { + return endpointPath; + } else { + return TemplateHtmlBuilder.adjustRoot(httpRoot.getRootPath(), endpointPath); + } + } + + public String getEndpointPath(NonApplicationRootPathBuildItem nonAppRoot) { + if (absolutePath) { + return endpointPath; + } else { + return TemplateHtmlBuilder.adjustRoot(nonAppRoot.getNormalizedHttpRootPath(), endpointPath); + } + } +} diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsole.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsole.java index a6b931815a4cc..559c9604dcfba 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsole.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsole.java @@ -44,8 +44,13 @@ public class DevConsole implements Handler { DevConsole(Engine engine, String httpRootPath, String frameworkRootPath) { this.engine = engine; + // Both of these paths will end in slash this.globalData.put("httpRootPath", httpRootPath); - this.globalData.put("frameworkRootPath", cleanFrameworkRootPath(frameworkRootPath)); + this.globalData.put("frameworkRootPath", frameworkRootPath); + + // This includes the dev segment, but does not include a trailing slash (for append) + this.globalData.put("devRootAppend", frameworkRootPath + "dev"); + this.globalData.put("quarkusVersion", Version.getVersion()); this.globalData.put("applicationName", config.getOptionalValue("quarkus.application.name", String.class).orElse("")); this.globalData.put("applicationVersion", @@ -94,19 +99,6 @@ public void handle(RoutingContext event) { } } - /** - * This removes the last / from the path - * - * @param p the path - * @return the path without the last / - */ - private String cleanFrameworkRootPath(String p) { - if (p != null && !p.isEmpty() && p.endsWith("/")) { - return p.substring(0, p.length() - 1); - } - return p; - } - private String getExtensionName(String namespace) { Map map = extensions.get(namespace); if (map == null) 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 88fe746024276..6d5c6d2da5fd5 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 @@ -256,19 +256,48 @@ public void runtimeTemplates(List items, @BuildStep @Record(ExecutionTime.STATIC_INIT) - public HistoryHandlerBuildItem hander(BuildProducer logHandlerBuildItemBuildProducer, + public HistoryHandlerBuildItem handler(BuildProducer logHandlerBuildItemBuildProducer, LogStreamRecorder recorder) { RuntimeValue> handler = recorder.handler(); logHandlerBuildItemBuildProducer.produce(new LogHandlerBuildItem((RuntimeValue) handler)); return new HistoryHandlerBuildItem(handler); } + @Consume(LoggingSetupBuildItem.class) + @BuildStep(onlyIf = IsDevelopment.class) + public ServiceStartBuildItem setupDeploymentSideHandling(List devTemplatePaths, + CurateOutcomeBuildItem curateOutcomeBuildItem, + List allRoutes, + List routes, + NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, LaunchModeBuildItem launchModeBuildItem) { + if (launchModeBuildItem.getDevModeType().orElse(null) != DevModeType.LOCAL) { + return null; + } + + initializeVirtual(); + Engine quteEngine = buildEngine(devTemplatePaths, + allRoutes, + nonApplicationRootPathBuildItem); + newRouter(quteEngine, nonApplicationRootPathBuildItem); + + for (DevConsoleRouteBuildItem i : routes) { + Entry groupAndArtifact = i.groupIdAndArtifactId(curateOutcomeBuildItem); + // deployment side handling + if (!(i.getHandler() instanceof BytecodeRecorderImpl.ReturnedProxy)) { + router.route(HttpMethod.valueOf(i.getMethod()), + "/" + groupAndArtifact.getKey() + "." + groupAndArtifact.getValue() + "/" + i.getPath()) + .handler(i.getHandler()); + } + } + + return null; + } + @Record(ExecutionTime.RUNTIME_INIT) @Consume(LoggingSetupBuildItem.class) @BuildStep(onlyIf = IsDevelopment.class) - public void setupActions(List routes, + public void setupDevConsoleRoutes(List routes, BuildProducer routeBuildItemBuildProducer, - List devTemplatePaths, LogStreamRecorder recorder, CurateOutcomeBuildItem curateOutcomeBuildItem, HistoryHandlerBuildItem historyHandlerBuildItem, @@ -277,9 +306,6 @@ public void setupActions(List routes, if (launchModeBuildItem.getDevModeType().orElse(null) != DevModeType.LOCAL) { return; } - initializeVirtual(); - - newRouter(buildEngine(devTemplatePaths), nonApplicationRootPathBuildItem); // Add the log stream routeBuildItemBuildProducer.produce(nonApplicationRootPathBuildItem.routeBuilder() @@ -290,6 +316,7 @@ public void setupActions(List routes, 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 + // otherwise this is handled in the setupDeploymentSideHandling method if (i.getHandler() instanceof BytecodeRecorderImpl.ReturnedProxy) { routeBuildItemBuildProducer.produce(nonApplicationRootPathBuildItem.routeBuilder() .routeFunction( @@ -297,10 +324,6 @@ public void setupActions(List routes, new RuntimeDevConsoleRoute(i.getMethod())) .handler(i.getHandler()) .build()); - } else { - router.route(HttpMethod.valueOf(i.getMethod()), - "/" + groupAndArtifact.getKey() + "." + groupAndArtifact.getValue() + "/" + i.getPath()) - .handler(i.getHandler()); } } @@ -340,7 +363,9 @@ public void deployStaticResources(DevConsoleRecorder recorder, CurateOutcomeBuil .build()); } - private Engine buildEngine(List devTemplatePaths) { + private Engine buildEngine(List devTemplatePaths, + List allRoutes, + NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) { EngineBuilder builder = Engine.builder().addDefaults(); // Escape some characters for HTML templates @@ -362,16 +387,35 @@ private Engine buildEngine(List devTemplatePaths) { return result == null ? Results.Result.NOT_FOUND : result; }).build()); + // Create map of resolved paths + Map resolvedPaths = new HashMap<>(); + for (RouteBuildItem item : allRoutes) { + ConfiguredPathInfo resolvedPathBuildItem = item.getDevConsoleResolvedPath(); + if (resolvedPathBuildItem != null) { + resolvedPaths.put(resolvedPathBuildItem.getName(), + resolvedPathBuildItem.getEndpointPath(nonApplicationRootPathBuildItem)); + } + } + // {config:property('quarkus.lambda.handler')} + // {config:http-path('quarkus.smallrye-graphql.ui.root-path')} + // Note that the output value is always string! builder.addNamespaceResolver(NamespaceResolver.builder("config").resolveAsync(ctx -> { List params = ctx.getParams(); - if (params.size() != 1 || !ctx.getName().equals("property")) { + if (params.size() != 1 || (!ctx.getName().equals("property") && !ctx.getName().equals("http-path"))) { return Results.NOT_FOUND; } - return ctx.evaluate(params.get(0)).thenCompose(propertyName -> { - Optional val = ConfigProvider.getConfig().getOptionalValue(propertyName.toString(), String.class); - return CompletableFuture.completedFuture(val.isPresent() ? val.get() : Result.NOT_FOUND); - }); + if (ctx.getName().equals("http-path")) { + return ctx.evaluate(params.get(0)).thenCompose(propertyName -> { + String value = resolvedPaths.get(propertyName.toString()); + return CompletableFuture.completedFuture(value != null ? value : Result.NOT_FOUND); + }); + } else { + return ctx.evaluate(params.get(0)).thenCompose(propertyName -> { + Optional val = ConfigProvider.getConfig().getOptionalValue(propertyName.toString(), String.class); + return CompletableFuture.completedFuture(val.isPresent() ? val.get() : Result.NOT_FOUND); + }); + } }).build()); // JavaDoc formatting diff --git a/extensions/vertx-http/deployment/src/main/resources/dev-templates/logmanagerNav.html b/extensions/vertx-http/deployment/src/main/resources/dev-templates/logmanagerNav.html index de8057fae3848..7aabed2c9dfee 100644 --- a/extensions/vertx-http/deployment/src/main/resources/dev-templates/logmanagerNav.html +++ b/extensions/vertx-http/deployment/src/main/resources/dev-templates/logmanagerNav.html @@ -1,6 +1,6 @@