Skip to content

Commit

Permalink
New Route SPI
Browse files Browse the repository at this point in the history
Unlike the current one, this SPI allows declaring routes without having
to depend on Quarkus Vert.x HTTP. Thus, if an extension declares a route
with this SPI, and Quarkus Vert.x HTTP is not present, the route is ignored.
However, if Quarkus Vert.x HTTP is present, the route is registered.
  • Loading branch information
cescoffier committed Jan 11, 2024
1 parent 3b727fa commit b8edb44
Show file tree
Hide file tree
Showing 7 changed files with 329 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
import io.quarkus.info.runtime.spi.InfoContributor;
import io.quarkus.maven.dependency.ResolvedDependency;
import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
import io.quarkus.vertx.http.deployment.RouteBuildItem;
import io.quarkus.vertx.http.deployment.spi.RouteBuildItem;

public class InfoProcessor {

Expand Down Expand Up @@ -281,13 +281,11 @@ RouteBuildItem defineRoute(InfoBuildTimeConfig buildTimeConfig,
List<InfoContributor> infoContributors = contributors.stream()
.map(InfoBuildTimeContributorBuildItem::getInfoContributor)
.collect(Collectors.toList());
return nonApplicationRootPathBuildItem.routeBuilder()
.management()
.route(buildTimeConfig.path())
.routeConfigKey("quarkus.info.path")
.handler(recorder.handler(buildTimeInfo, infoContributors))
return RouteBuildItem.newManagementRoute(buildTimeConfig.path())
.withRoutePathConfigKey("quarkus.info.path")
.withRequestHandler(recorder.handler(buildTimeInfo, infoContributors))
.displayOnNotFoundPage()
.blockingRoute()
.asBlockingRoute()
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@
import io.quarkus.vertx.http.deployment.FilterBuildItem;
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.SecurityInformationBuildItem;
import io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem;
import io.quarkus.vertx.http.deployment.spi.RouteBuildItem;
import io.quarkus.vertx.http.runtime.management.ManagementInterfaceBuildTimeConfig;
import io.quarkus.vertx.http.runtime.management.ManagementInterfaceConfiguration;
import io.smallrye.openapi.api.OpenApiConfig;
Expand Down Expand Up @@ -305,32 +305,31 @@ void handler(LaunchModeBuildItem launch,
}
}

routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
.management("quarkus.smallrye-openapi.management.enabled")
.routeFunction(openApiConfig.path, corsFilter)
.routeConfigKey("quarkus.smallrye-openapi.path")
.handler(handler)
routes.produce(RouteBuildItem.newManagementRoute(openApiConfig.path, "quarkus.smallrye-openapi.management.enabled")
.withRouteCustomizer(corsFilter)
.withRoutePathConfigKey("quarkus.smallrye-openapi.path")
.withRequestHandler(handler)
.displayOnNotFoundPage("Open API Schema document")
.blockingRoute()
.asBlockingRoute()
.build());

routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
.management("quarkus.smallrye-openapi.management.enabled")
.routeFunction(openApiConfig.path + ".json", corsFilter)
.handler(handler)
.build());

routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
.management("quarkus.smallrye-openapi.management.enabled")
.routeFunction(openApiConfig.path + ".yaml", corsFilter)
.handler(handler)
.build());

routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
.management("quarkus.smallrye-openapi.management.enabled")
.routeFunction(openApiConfig.path + ".yml", corsFilter)
.handler(handler)
.build());
routes.produce(
RouteBuildItem.newManagementRoute(openApiConfig.path + ".json", "quarkus.smallrye-openapi.management.enabled")
.withRouteCustomizer(corsFilter)
.withRequestHandler(handler)
.build());

routes.produce(
RouteBuildItem.newManagementRoute(openApiConfig.path + ".yaml", "quarkus.smallrye-openapi.management.enabled")
.withRouteCustomizer(corsFilter)
.withRequestHandler(handler)
.build());

routes.produce(
RouteBuildItem.newManagementRoute(openApiConfig.path + ".yml", "quarkus.smallrye-openapi.management.enabled")
.withRouteCustomizer(corsFilter)
.withRequestHandler(handler)
.build());

// If management is enabled and swagger-ui is part of management, we need to add CORS so that swagger can hit the endpoint
if (isManagement(managementInterfaceBuildTimeConfig, openApiConfig, launch)) {
Expand Down
5 changes: 5 additions & 0 deletions extensions/vertx-http/deployment-spi/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core-deployment</artifactId>
</dependency>

<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
package io.quarkus.vertx.http.deployment.spi;

import java.util.OptionalInt;
import java.util.function.Consumer;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;

import io.quarkus.builder.item.MultiBuildItem;
import io.vertx.core.Handler;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.RoutingContext;

/**
* A build item that represents a route that should be added to the router.
* <p>
* Producing this build item does not mean the HTTP server is available.
* It will be consumed if the Quarkus Vert.x HTTP extension is present.
*/
public final class RouteBuildItem extends MultiBuildItem {

/**
* The type of route handler
*/
public enum HandlerType {

/**
* A regular route handler invoked on the event loop.
*
* @see io.vertx.ext.web.Route#handler(Handler)
*/
NORMAL,
/**
* A blocking route handler, invoked on a worker thread.
*
* @see io.vertx.ext.web.Route#blockingHandler(Handler)
*/
BLOCKING,
/**
* A failure handler, invoked when an exception is thrown from a route handler.
* This is invoked on the event loop.
*
* @see io.vertx.ext.web.Route#failureHandler(Handler)
*/
FAILURE

}

/**
* Type of routes.
*/
public enum RouteType {

/**
* Framework routes are provided by the Quarkus framework (or extensions).
* They are not related to the application business logic, but provide a non-functional feature (health, metrics...).
* <p>
* Framework route can be mounted on the application router (under the non application route path) or on the management
* router when enabled.
*/
FRAMEWORK_ROUTE,
/**
* Application routes are part of the application business logic.
* They are mounted on the application router (so the application prefix is applied).
*/
APPLICATION_ROUTE,
/**
* Absolute routes are part of the application business logic, and are mounted on the root router (exposed on /).
*/
ABSOLUTE_ROUTE
}

private RouteType typeOfRoute = RouteType.APPLICATION_ROUTE;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private OptionalInt order = OptionalInt.empty();

private String path;
private Consumer<Route> customizer;

private boolean isManagement;

private Handler<RoutingContext> handler;

private HandlerType typeOfHandler = HandlerType.NORMAL;

private boolean displayOnNotFoundPage;
private String notFoundPageTitle;

private String routeConfigKey;

public RouteType getTypeOfRoute() {
return typeOfRoute;
}

public boolean hasOrder() {
return order.isPresent();
}

public int getOrder() {
if (order.isPresent()) {
return order.getAsInt();
} else {
throw new IllegalStateException("No order set");
}
}

public boolean hasRouteConfigKey() {
return routeConfigKey != null;
}

public String getRouteConfigKey() {
return routeConfigKey;
}

public Handler<RoutingContext> getHandler() {
return handler;
}

public HandlerType getHandlerType() {
return typeOfHandler;
}

public String getPath() {
return path;
}

public Consumer<Route> getCustomizer() {
return customizer;
}

public String getNotFoundPageTitle() {
return notFoundPageTitle;
}

public boolean isDisplayOnNotFoundPage() {
return displayOnNotFoundPage;
}

public static Builder newApplicationRoute(String path) {
return new Builder(RouteType.APPLICATION_ROUTE, path, false);
}

public static Builder newAbsoluteRoute(String path) {
return new Builder(RouteType.ABSOLUTE_ROUTE, path, false);
}

public static Builder newFrameworkRoute(String path) {
return new Builder(RouteType.FRAMEWORK_ROUTE, path, false);
}

public static Builder newManagementRoute(String path) {
return new Builder(RouteType.FRAMEWORK_ROUTE, path, true);
}

public static Builder newManagementRoute(String path, String managementConfigKey) {
return new Builder(RouteType.FRAMEWORK_ROUTE, path,
(managementConfigKey == null || shouldInclude(managementConfigKey)));
}

private static boolean shouldInclude(String managementConfigKey) {
Config config = ConfigProvider.getConfig();
return config.getValue(managementConfigKey, boolean.class);
}

public boolean isManagement() {
return isManagement;
}

public static class Builder {

private final RouteBuildItem item;

private Builder(RouteType type, String path, boolean isManagement) {
item = new RouteBuildItem();
item.typeOfRoute = type;
item.path = path;
item.isManagement = isManagement;
}

public Builder withRouteCustomizer(Consumer<Route> customizer) {
item.customizer = customizer;
return this;
}

public Builder withOrder(int order) {
item.order = OptionalInt.of(order);
return this;
}

public Builder withRequestHandler(Handler<RoutingContext> handler) {
item.handler = handler;
return this;
}

public Builder asBlockingRoute() {
item.typeOfHandler = HandlerType.BLOCKING;
return this;
}

public Builder failureRoute() {
item.typeOfHandler = HandlerType.FAILURE;
return this;
}

public Builder displayOnNotFoundPage() {
item.displayOnNotFoundPage = true;
return this;
}

public Builder displayOnNotFoundPage(String notFoundPageTitle) {
item.displayOnNotFoundPage = true;
item.notFoundPageTitle = notFoundPageTitle;
return this;
}

public Builder withRoutePathConfigKey(String attributeName) {
item.routeConfigKey = attributeName;
return this;
}

public RouteBuildItem build() {
if (item.handler == null) {
throw new IllegalArgumentException("The route handler must be set");
}

return item;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.quarkus.vertx.http.deployment;

import io.quarkus.vertx.http.runtime.HandlerType;

/**
* Convert the route build item from the SPI to the internal representation
*/
public class RouteConverter {

public static RouteBuildItem convert(io.quarkus.vertx.http.deployment.spi.RouteBuildItem item,
HttpRootPathBuildItem httpRootPathBuildItem,
NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) {
// The builder depends on the type of route
RouteBuildItem.Builder builder;
if (item.getTypeOfRoute() == io.quarkus.vertx.http.deployment.spi.RouteBuildItem.RouteType.FRAMEWORK_ROUTE) {
builder = nonApplicationRootPathBuildItem.routeBuilder();
} else {
builder = httpRootPathBuildItem.routeBuilder();
}

if (item.isManagement()) {
builder = builder.management();
}
if (item.hasRouteConfigKey()) {
builder = builder.routeConfigKey(item.getRouteConfigKey());
}

builder = builder.handler(item.getHandler()).handlerType(HandlerType.valueOf(item.getHandlerType().name()));
if (item.isDisplayOnNotFoundPage()) {
builder = builder
.displayOnNotFoundPage(item.getNotFoundPageTitle());
}

if (item.hasOrder()) {
builder = builder.orderedRoute(item.getPath(), item.getOrder(), item.getCustomizer());
} else {
builder = builder.routeFunction(item.getPath(), item.getCustomizer());
}

return builder.build();

}

}
Loading

0 comments on commit b8edb44

Please sign in to comment.