Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow extensions to contribute actions to the error page #42171

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading