Skip to content

Commit

Permalink
Add decorate to all contents types and added text base error response
Browse files Browse the repository at this point in the history
Signed-off-by: Phillip Kruger <[email protected]>
  • Loading branch information
phillip-kruger committed Sep 2, 2024
1 parent b702e31 commit a3ced40
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -327,5 +327,6 @@ public enum InsecureRequests {
public enum PayloadHint {
JSON,
HTML,
TEXT
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package io.quarkus.vertx.http.runtime;

import static io.quarkus.vertx.http.runtime.HttpConfiguration.PayloadHint.JSON;
import static org.jboss.logging.Logger.getLogger;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.RejectedExecutionException;
Expand All @@ -20,6 +23,7 @@
import io.netty.handler.codec.http.HttpResponseStatus;
import io.quarkus.runtime.ErrorPageAction;
import io.quarkus.runtime.TemplateHtmlBuilder;
import io.quarkus.runtime.logging.DecorateStackUtil;
import io.quarkus.security.AuthenticationException;
import io.quarkus.security.ForbiddenException;
import io.quarkus.security.UnauthorizedException;
Expand All @@ -32,6 +36,9 @@
public class QuarkusErrorHandler implements Handler<RoutingContext> {

private static final Logger log = getLogger(QuarkusErrorHandler.class);
private static final String NL = "\n";
private static final String TAB = "\t";
private static final String HEADING = "500 - Internal Server Error";

/**
* we don't want to generate a new UUID each time as it is slowish. Instead, we just generate one based one
Expand Down Expand Up @@ -172,6 +179,7 @@ public void accept(Throwable throwable) {
if (responseContentType == null) {
responseContentType = "";
}

switch (responseContentType) {
case ContentTypes.TEXT_HTML:
case ContentTypes.APPLICATION_XHTML:
Expand All @@ -181,30 +189,94 @@ public void accept(Throwable throwable) {
break;
case ContentTypes.APPLICATION_JSON:
case ContentTypes.TEXT_JSON:
jsonResponse(event, responseContentType, details, stack);
jsonResponse(event, responseContentType, details, stack, exception);
break;
case ContentTypes.TEXT_PLAIN:
textResponse(event, details, stack, exception);
break;
default:
// We default to JSON representation
switch (contentTypeDefault.orElse(HttpConfiguration.PayloadHint.JSON)) {
case HTML:
htmlResponse(event, details, exception);
break;
case JSON:
default:
jsonResponse(event, ContentTypes.APPLICATION_JSON, details, stack);
break;
if (contentTypeDefault.isPresent()) {
switch (contentTypeDefault.get()) {
case HTML:
htmlResponse(event, details, exception);
break;
case JSON:
jsonResponse(event, ContentTypes.APPLICATION_JSON, details, stack, exception);
break;
case TEXT:
textResponse(event, details, stack, exception);
break;
default:
defaultResponse(event, details, stack, exception);
break;
}
} else {
defaultResponse(event, details, stack, exception);
break;
}
break;
}
}

private void jsonResponse(RoutingContext event, String contentType, String details, String stack) {
private void defaultResponse(RoutingContext event, String details, String stack, Throwable throwable) {
String userAgent = event.request().getHeader("User-Agent");
if (userAgent != null && (userAgent.toLowerCase(Locale.ROOT).startsWith("wget/")
|| userAgent.toLowerCase(Locale.ROOT).startsWith("curl/"))) {
textResponse(event, details, stack, throwable);
} else {
jsonResponse(event, ContentTypes.APPLICATION_JSON, details, stack, throwable);
}
}

private void textResponse(RoutingContext event, String details, String stack, Throwable throwable) {
event.response().headers().set(HttpHeaderNames.CONTENT_TYPE, ContentTypes.TEXT_PLAIN + "; charset=utf-8");
String decoratedString = null;
if (decorateStack && throwable != null) {
decoratedString = DecorateStackUtil.getDecoratedString(throwable, srcMainJava, knowClasses);
}

try (StringWriter sw = new StringWriter()) {
sw.write(NL + HEADING + NL);
sw.write("------------------------" + NL);
sw.write(NL);
sw.write("Details:");
sw.write(NL);
sw.write(TAB + details);
sw.write(NL);
if (decoratedString != null) {
sw.write("Decorate:");
sw.write(NL);
sw.write(TAB + decoratedString);
sw.write(NL);
}
sw.write("Stack:");
sw.write(NL);
sw.write(TAB + stack);
sw.write(NL);
writeResponse(event, sw.toString());
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}

private void jsonResponse(RoutingContext event, String contentType, String details, String stack, Throwable throwable) {
event.response().headers().set(HttpHeaderNames.CONTENT_TYPE, contentType + "; charset=utf-8");
String escapedDetails = escapeJsonString(details);
String escapedStack = escapeJsonString(stack);
String decoratedString = null;
if (decorateStack && throwable != null) {
decoratedString = DecorateStackUtil.getDecoratedString(throwable, srcMainJava, knowClasses);
}

StringBuilder jsonPayload = new StringBuilder("{\"details\":\"")
.append(escapedDetails)
.append("\",\"stack\":\"")
.append(escapedDetails);

if (decoratedString != null) {
jsonPayload = jsonPayload.append("\",\"decorate\":\"")
.append(escapeJsonString(decoratedString));
}

jsonPayload = jsonPayload.append("\",\"stack\":\"")
.append(escapedStack)
.append("\"}");
writeResponse(event, jsonPayload.toString());
Expand Down Expand Up @@ -297,12 +369,14 @@ private ContentTypes() {
private static final String APPLICATION_JSON = "application/json";
private static final String TEXT_JSON = "text/json";
private static final String TEXT_HTML = "text/html";
private static final String TEXT_PLAIN = "text/plain";
private static final String APPLICATION_XHTML = "application/xhtml+xml";
private static final String APPLICATION_XML = "application/xml";
private static final String TEXT_XML = "text/xml";

// WARNING: The order matters for wildcards: if text/json is before text/html, then text/* will match text/json.
private static final Collection<MIMEHeader> SUPPORTED = Arrays.asList(
new ParsableMIMEValue(TEXT_PLAIN).forceParse(),
new ParsableMIMEValue(APPLICATION_JSON).forceParse(),
new ParsableMIMEValue(TEXT_JSON).forceParse(),
new ParsableMIMEValue(TEXT_HTML).forceParse(),
Expand Down

0 comments on commit a3ced40

Please sign in to comment.