Skip to content

Commit

Permalink
Allow extensions to contribute actions to the error page
Browse files Browse the repository at this point in the history
Signed-off-by: Phillip Kruger <[email protected]>
  • Loading branch information
phillip-kruger committed Jul 26, 2024
1 parent f9f95ca commit 7409649
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.quarkus.runtime;

public record ErrorPageAction(String name, String url) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ public class TemplateHtmlBuilder {
+ "<header>\n" +
" <div class=\"exception-message\">\n" +
" <h2 class=\"container\">%2$s</h2>\n" +
" <div class=\"actions\">%3$s</div>\n" +
" </div>\n" +
"</header>\n" +
"<div class=\"container content\">\n";
Expand Down Expand Up @@ -176,25 +177,37 @@ public class TemplateHtmlBuilder {
private String baseUrl;

public TemplateHtmlBuilder(String title, String subTitle, String details) {
this(null, title, subTitle, details, null, Collections.emptyList());
this(null, title, subTitle, details, Collections.emptyList(), null, Collections.emptyList());
}

public TemplateHtmlBuilder(String baseUrl, String title, String subTitle, String details) {
this(baseUrl, title, subTitle, details, null, Collections.emptyList());
public TemplateHtmlBuilder(String title, String subTitle, String details, List<ErrorPageAction> actions) {
this(null, title, subTitle, details, actions, null, Collections.emptyList());
}

public TemplateHtmlBuilder(String title, String subTitle, String details, String redirect,
public TemplateHtmlBuilder(String baseUrl, String title, String subTitle, String details, List<ErrorPageAction> actions) {
this(baseUrl, title, subTitle, details, actions, null, Collections.emptyList());
}

public TemplateHtmlBuilder(String title, String subTitle, String details, List<ErrorPageAction> actions, String redirect,
List<CurrentConfig> config) {
this(null, title, subTitle, details, null, Collections.emptyList());
this(null, title, subTitle, details, actions, null, Collections.emptyList());
}

public TemplateHtmlBuilder(String baseUrl, String title, String subTitle, String details, String redirect,
public TemplateHtmlBuilder(String baseUrl, String title, String subTitle, String details, List<ErrorPageAction> actions,
String redirect,
List<CurrentConfig> config) {
this.baseUrl = baseUrl;

loadCssFile();

StringBuilder actionLinks = new StringBuilder();
for (ErrorPageAction epa : actions) {
actionLinks.append(buildLink(epa.name(), epa.url()));
}

result = new StringBuilder(String.format(HTML_TEMPLATE_START, escapeHtml(title),
subTitle == null || subTitle.isEmpty() ? "" : " - " + escapeHtml(subTitle), CSS));
result.append(String.format(HEADER_TEMPLATE, escapeHtml(title), escapeHtml(details)));
result.append(String.format(HEADER_TEMPLATE, escapeHtml(title), escapeHtml(details), actionLinks.toString()));
if (!config.isEmpty()) {
result.append(String.format(CONFIG_EDITOR_HEAD, redirect));
for (CurrentConfig i : config) {
Expand Down Expand Up @@ -379,4 +392,8 @@ public void loadCssFile() {
}
}
}

private String buildLink(String name, String url) {
return "<a href=" + url + ">" + name + "</a>";
}
}
19 changes: 19 additions & 0 deletions core/runtime/src/main/resources/META-INF/template-html-builder.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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

import java.util.List;

import io.quarkus.builder.item.MultiBuildItem;
import io.quarkus.runtime.ErrorPageAction;

/**
* Allows extensions to contribute an action (button) to the error page
*/
public final class ErrorPageActionsBuildItem extends MultiBuildItem {
private final List<ErrorPageAction> actions;

public ErrorPageActionsBuildItem(String name, String url) {
this(new ErrorPageAction(name, url));
}

public ErrorPageActionsBuildItem(ErrorPageAction errorPageAction) {
this(List.of(errorPageAction));
}

public ErrorPageActionsBuildItem(List<ErrorPageAction> errorPageAction) {
this.actions = errorPageAction;
}

public List<ErrorPageAction> getActions() {
return actions;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import io.quarkus.deployment.pkg.steps.NoopNativeImageBuildRunner;
import io.quarkus.kubernetes.spi.KubernetesPortBuildItem;
import io.quarkus.netty.runtime.virtual.VirtualServerChannel;
import io.quarkus.runtime.ErrorPageAction;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.LiveReloadConfig;
import io.quarkus.runtime.RuntimeValue;
Expand Down Expand Up @@ -338,6 +339,7 @@ ServiceStartBuildItem finalizeRouter(
HttpBuildTimeConfig httpBuildTimeConfig,
List<RequireBodyHandlerBuildItem> requireBodyHandlerBuildItems,
BodyHandlerBuildItem bodyHandlerBuildItem,
List<ErrorPageActionsBuildItem> errorPageActionsBuildItems,
BuildProducer<ShutdownListenerBuildItem> shutdownListenerBuildItemBuildProducer,
ShutdownConfig shutdownConfig,
LiveReloadConfig lrc,
Expand Down Expand Up @@ -391,6 +393,12 @@ ServiceStartBuildItem finalizeRouter(
}
}

// Combine all error actions from exceptions
List<ErrorPageAction> combinedActions = new ArrayList<>();
for (ErrorPageActionsBuildItem errorPageActionsBuildItem : errorPageActionsBuildItems) {
combinedActions.addAll(errorPageActionsBuildItem.getActions());
}

recorder.finalizeRouter(beanContainer.getValue(),
defaultRoute.map(DefaultRouteBuildItem::getRoute).orElse(null),
listOfFilters, listOfManagementInterfaceFilters,
Expand All @@ -401,7 +409,8 @@ ServiceStartBuildItem finalizeRouter(
nonApplicationRootPathBuildItem.getNonApplicationRootPath(),
launchMode.getLaunchMode(),
getBodyHandlerRequiredConditions(requireBodyHandlerBuildItems), bodyHandlerBuildItem.getHandler(),
gracefulShutdownFilter, shutdownConfig, executorBuildItem.getExecutorProxy());
gracefulShutdownFilter, shutdownConfig, executorBuildItem.getExecutorProxy(),
combinedActions);

return new ServiceStartBuildItem("vertx-http");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.quarkus.runtime.ErrorPageAction;
import io.quarkus.runtime.TemplateHtmlBuilder;
import io.quarkus.security.AuthenticationException;
import io.quarkus.security.ForbiddenException;
Expand All @@ -43,12 +44,20 @@ public class QuarkusErrorHandler implements Handler<RoutingContext> {
private final boolean showStack;
private final boolean decorateStack;
private final Optional<HttpConfiguration.PayloadHint> contentTypeDefault;
private final List<ErrorPageAction> actions;

public QuarkusErrorHandler(boolean showStack, boolean decorateStack,
Optional<HttpConfiguration.PayloadHint> contentTypeDefault) {
this(showStack, decorateStack, contentTypeDefault, List.of());
}

public QuarkusErrorHandler(boolean showStack, boolean decorateStack,
Optional<HttpConfiguration.PayloadHint> contentTypeDefault,
List<ErrorPageAction> actions) {
this.showStack = showStack;
this.decorateStack = decorateStack;
this.contentTypeDefault = contentTypeDefault;
this.actions = actions;
}

@Override
Expand Down Expand Up @@ -200,10 +209,12 @@ private void jsonResponse(RoutingContext event, String contentType, String detai

private void htmlResponse(RoutingContext event, String details, Throwable exception) {
event.response().headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=utf-8");
final TemplateHtmlBuilder htmlBuilder = new TemplateHtmlBuilder("Internal Server Error", details, details);
final TemplateHtmlBuilder htmlBuilder = new TemplateHtmlBuilder("Internal Server Error", details, details,
this.actions);
if (showStack && exception != null) {
htmlBuilder.stack(exception);
}

writeResponse(event, htmlBuilder.toString());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import io.quarkus.netty.runtime.virtual.VirtualAddress;
import io.quarkus.netty.runtime.virtual.VirtualChannel;
import io.quarkus.netty.runtime.virtual.VirtualServerChannel;
import io.quarkus.runtime.ErrorPageAction;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.LiveReloadConfig;
import io.quarkus.runtime.QuarkusBindException;
Expand Down Expand Up @@ -377,7 +378,8 @@ public void finalizeRouter(BeanContainer container, Consumer<Route> defaultRoute
LaunchMode launchMode, BooleanSupplier[] requireBodyHandlerConditions,
Handler<RoutingContext> bodyHandler,
GracefulShutdownFilter gracefulShutdownFilter, ShutdownConfig shutdownConfig,
Executor executor) {
Executor executor,
List<ErrorPageAction> actions) {
HttpConfiguration httpConfiguration = this.httpConfiguration.getValue();
// install the default route at the end
Router httpRouteRouter = httpRouterRuntimeValue.getValue();
Expand Down Expand Up @@ -415,8 +417,7 @@ public void finalizeRouter(BeanContainer container, Consumer<Route> defaultRoute
applyCompression(httpBuildTimeConfig.enableCompression, httpRouteRouter);
httpRouteRouter.route().last().failureHandler(
new QuarkusErrorHandler(launchMode.isDevOrTest(), decorateStacktrace(launchMode, httpConfiguration),
httpConfiguration.unhandledErrorContentTypeDefault));

httpConfiguration.unhandledErrorContentTypeDefault, actions));
for (BooleanSupplier requireBodyHandlerCondition : requireBodyHandlerConditions) {
if (requireBodyHandlerCondition.getAsBoolean()) {
//if this is set then everything needs the body handler installed
Expand Down Expand Up @@ -535,7 +536,7 @@ public void handle(RoutingContext event) {

mr.route().last().failureHandler(
new QuarkusErrorHandler(launchMode.isDevOrTest(), decorateStacktrace(launchMode, httpConfiguration),
httpConfiguration.unhandledErrorContentTypeDefault));
httpConfiguration.unhandledErrorContentTypeDefault, actions));

mr.route().order(RouteConstants.ROUTE_ORDER_BODY_HANDLER_MANAGEMENT)
.handler(createBodyHandlerForManagementInterface());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public static String generateHtml(final Throwable exception, String currentUri)
}

TemplateHtmlBuilder builder = new TemplateHtmlBuilder("Error restarting Quarkus", exception.getClass().getName(),
generateHeaderMessage(exception), currentUri, toEdit);
generateHeaderMessage(exception), List.of(), currentUri, toEdit);
builder.stack(exception);
return builder.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public String getHTMLContent() {

List<RouteDescription> combinedRoutes = getCombinedRoutes();
TemplateHtmlBuilder builder = new TemplateHtmlBuilder(this.baseUrl,
HEADING, "", "Resources overview");
HEADING, "", "Resources overview", List.of());

builder.resourcesStart(RESOURCE_ENDPOINTS);

Expand Down

0 comments on commit 7409649

Please sign in to comment.