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 <phillip.kruger@gmail.com>
  • Loading branch information
phillip-kruger committed Sep 2, 2024
1 parent 70ab415 commit 19503f7
Showing 2 changed files with 109 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -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,17 @@
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.ArrayList;
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;
@@ -21,6 +25,7 @@
import io.quarkus.runtime.ErrorPageAction;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.TemplateHtmlBuilder;
import io.quarkus.runtime.logging.DecorateStackUtil;
import io.quarkus.security.AuthenticationCompletionException;
import io.quarkus.security.AuthenticationException;
import io.quarkus.security.ForbiddenException;
@@ -34,6 +39,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
@@ -181,6 +189,7 @@ public void accept(Throwable throwable) {
if (responseContentType == null) {
responseContentType = "";
}

switch (responseContentType) {
case ContentTypes.TEXT_HTML:
case ContentTypes.APPLICATION_XHTML:
@@ -190,30 +199,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 (Source code):");
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());
@@ -306,23 +379,41 @@ 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(
private static final MIMEHeader[] BASE_HEADERS = {
new ParsableMIMEValue(APPLICATION_JSON).forceParse(),
new ParsableMIMEValue(TEXT_JSON).forceParse(),
new ParsableMIMEValue(TEXT_HTML).forceParse(),
new ParsableMIMEValue(APPLICATION_XHTML).forceParse(),
new ParsableMIMEValue(APPLICATION_XML).forceParse(),
new ParsableMIMEValue(TEXT_XML).forceParse());
new ParsableMIMEValue(TEXT_XML).forceParse()
};

private static final Collection<MIMEHeader> SUPPORTED = new ArrayList<>(Arrays.asList(BASE_HEADERS));
private static final Collection<MIMEHeader> SUPPORTED_CURL = new ArrayList<>();
static {
SUPPORTED_CURL.add(new ParsableMIMEValue(TEXT_PLAIN).forceParse());
SUPPORTED_CURL.addAll(Arrays.asList(BASE_HEADERS));
((ArrayList<MIMEHeader>) SUPPORTED).add(new ParsableMIMEValue(TEXT_PLAIN).forceParse());
}

static String pickFirstSupportedAndAcceptedContentType(RoutingContext context) {
List<MIMEHeader> acceptableTypes = context.parsedHeaders().accept();
MIMEHeader result = context.parsedHeaders().findBestUserAcceptedIn(acceptableTypes, SUPPORTED);
return result == null ? null : result.value();

String userAgent = context.request().getHeader("User-Agent");
if (userAgent != null && (userAgent.toLowerCase(Locale.ROOT).startsWith("wget/")
|| userAgent.toLowerCase(Locale.ROOT).startsWith("curl/"))) {
MIMEHeader result = context.parsedHeaders().findBestUserAcceptedIn(acceptableTypes, SUPPORTED_CURL);
return result == null ? null : result.value();
} else {
MIMEHeader result = context.parsedHeaders().findBestUserAcceptedIn(acceptableTypes, SUPPORTED);
return result == null ? null : result.value();
}
}
}
}

0 comments on commit 19503f7

Please sign in to comment.