Allow configuring showCause in ErrorHandler (#11587)

Allows to suppress "caused by" in error message, not only the stacks traces.
ErrorHandler.showCause = false by default.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
Co-authored-by: Dennis Hoersch <dennis.hoersch@springernature.com>
Co-authored-by: Dennis Hoersch <dhs3000+ghtu@posteo.de>
This commit is contained in:
Simone Bordet 2024-03-29 16:37:56 +01:00 committed by GitHub
parent 0451bd4b86
commit 1b05d49fd8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 113 additions and 20 deletions

View File

@ -71,6 +71,7 @@ public class ErrorHandler implements Request.Handler
public static final HttpField ERROR_CACHE_CONTROL = new PreEncodedHttpField(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
boolean _showStacks = false;
boolean _showCauses = false;
boolean _showMessageInTitle = true;
String _defaultResponseMimeType = Type.TEXT_HTML.asString();
HttpField _cacheControl = new PreEncodedHttpField(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
@ -321,7 +322,7 @@ public class ErrorHandler implements Request.Handler
htmlRow(writer, "URI", uri);
htmlRow(writer, "STATUS", status);
htmlRow(writer, "MESSAGE", message);
while (cause != null)
while (_showCauses && cause != null)
{
htmlRow(writer, "CAUSED BY", cause);
cause = cause.getCause();
@ -354,7 +355,7 @@ public class ErrorHandler implements Request.Handler
writer.printf("URI: %s%n", request.getHttpURI());
writer.printf("STATUS: %s%n", code);
writer.printf("MESSAGE: %s%n", message);
while (cause != null)
while (_showCauses && cause != null)
{
writer.printf("CAUSED BY %s%n", cause);
if (showStacks)
@ -371,7 +372,7 @@ public class ErrorHandler implements Request.Handler
json.put("status", Integer.toString(code));
json.put("message", message);
int c = 0;
while (cause != null)
while (_showCauses && cause != null)
{
json.put("cause" + c++, cause.toString());
cause = cause.getCause();
@ -458,6 +459,23 @@ public class ErrorHandler implements Request.Handler
_showStacks = showStacks;
}
/**
* @return True if exception causes are shown in the error pages
*/
@ManagedAttribute("Whether the error page shows the exception causes")
public boolean isShowCauses()
{
return _showCauses;
}
/**
* @param showCauses True if exception causes are shown in the error pages
*/
public void setShowCauses(boolean showCauses)
{
_showCauses = showCauses;
}
@ManagedAttribute("Whether the error message is shown in the error page title")
public boolean isShowMessageInTitle()
{

View File

@ -481,6 +481,82 @@ public class ErrorHandlerTest
assertContent(response);
}
@Test
public void testContainsNoStacksByDefault() throws Exception
{
String rawResponse = connector.getResponse("""
GET /badmessage/444 HTTP/1.1
Host: Localhost
""");
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat("Response status code", response.getStatus(), is(444));
assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0));
assertThat(response.getContent(), not(containsString("<h3>Caused by:</h3>")));
assertThat(response.getContent(), not(containsString("org.eclipse.jetty.server.ErrorHandlerTest$1.handle")));
}
@Test
public void testContainsStacksIfSpecified() throws Exception
{
ErrorHandler errorHandler = new ErrorHandler();
errorHandler.setShowStacks(true);
server.setErrorHandler(errorHandler);
String rawResponse = connector.getResponse("""
GET /badmessage/444 HTTP/1.1
Host: Localhost
""");
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat("Response status code", response.getStatus(), is(444));
assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0));
assertThat(response.getContent(), containsString("<h3>Caused by:</h3>"));
assertThat(response.getContent(), containsString("org.eclipse.jetty.server.ErrorHandlerTest$1.handle"));
}
@Test
public void testContainsNoCausesByDefault() throws Exception
{
String rawResponse = connector.getResponse("""
GET /badmessage/444 HTTP/1.1
Host: Localhost
""");
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat("Response status code", response.getStatus(), is(444));
assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0));
assertThat(response.getContent(), not(containsString("<th>CAUSED BY:</th>")));
assertThat(response.getContent(), not(containsString("<td>org.eclipse.jetty.http.BadMessageException: 444: null</td>")));
}
@Test
public void testContainsCausesIfSpecified() throws Exception
{
ErrorHandler errorHandler = new ErrorHandler();
errorHandler.setShowCauses(true);
server.setErrorHandler(errorHandler);
String rawResponse = connector.getResponse("""
GET /badmessage/444 HTTP/1.1
Host: Localhost
""");
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat("Response status code", response.getStatus(), is(444));
assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0));
assertThat(response.getContent(), containsString("<th>CAUSED BY:</th>"));
assertThat(response.getContent(), containsString("<td>org.eclipse.jetty.http.BadMessageException: 444: null</td>"));
}
@Test
public void testNoBodyErrorHandler() throws Exception
{

View File

@ -26,6 +26,8 @@ import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.UrlEncoded;
import org.eclipse.jetty.util.component.LifeCycle;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@ -58,7 +60,7 @@ public class ServerHttpCookieTest
_server.setHandler(new Handler.Abstract()
{
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
public boolean handle(Request request, Response response, Callback callback)
{
Fields parameters = Request.extractQueryParameters(request);
Fields.Field setCookie = parameters.get("SetCookie");
@ -79,11 +81,17 @@ public class ServerHttpCookieTest
}
});
HttpConnectionFactory httpConnectionFactory = _connector.getConnectionFactory(HttpConnectionFactory.class);
httpConnectionFactory.setRecordHttpComplianceViolations(true);
_httpConfiguration = httpConnectionFactory.getHttpConfiguration();
_httpConfiguration.addComplianceViolationListener(new ComplianceViolation.LoggingListener());
_server.start();
}
@AfterEach
public void afterEach()
{
LifeCycle.stop(_server);
}
public static Stream<Arguments> requestCases()
{
return Stream.of(
@ -101,7 +109,7 @@ public class ServerHttpCookieTest
// multiple cookie tests
Arguments.of(RFC6265_STRICT, "Cookie: name=value; other=extra", 200, "Version=", List.of("[name=value]", "[other=extra]").toArray(new String[0])),
Arguments.of(RFC6265_STRICT, "Cookie: name=value, other=extra", 400, null, List.of("BadMessageException", "Comma cookie separator").toArray(new String[0])),
Arguments.of(RFC6265_STRICT, "Cookie: name=value, other=extra", 400, null, List.of("400", "Comma cookie separator").toArray(new String[0])),
Arguments.of(from("RFC6265_STRICT,COMMA_SEPARATOR,"), "Cookie: name=value, other=extra", 200, "Version=", List.of("[name=value]", "[other=extra]").toArray(new String[0])),
Arguments.of(RFC6265, "Cookie: name=value, other=extra", 200, "name=value", null),
Arguments.of(RFC6265_LEGACY, "Cookie: name=value, other=extra", 200, null, List.of("[name=value, other=extra]").toArray(new String[0])),
@ -110,13 +118,13 @@ public class ServerHttpCookieTest
Arguments.of(RFC2965_LEGACY, "Cookie: name=value, other=extra", 200, "Version=", List.of("[name=value]", "[other=extra]").toArray(new String[0])),
// white space
Arguments.of(RFC6265_STRICT, "Cookie: name =value", 400, null, List.of("BadMessageException", "Bad Cookie name").toArray(new String[0])),
Arguments.of(RFC6265_STRICT, "Cookie: name =value", 400, null, List.of("400", "Bad Cookie name").toArray(new String[0])),
Arguments.of(from("RFC6265,OPTIONAL_WHITE_SPACE"), "Cookie: name =value", 200, null, List.of("name=value").toArray(new String[0])),
// bad characters
Arguments.of(RFC6265_STRICT, "Cookie: name=va\\ue", 400, null, List.of("BadMessageException", "Bad Cookie value").toArray(new String[0])),
Arguments.of(RFC6265_STRICT, "Cookie: name=va\\ue", 400, null, List.of("400", "Bad Cookie value").toArray(new String[0])),
Arguments.of(RFC6265_STRICT, "Cookie: name=\"value\"", 200, "Version=", List.of("[name=value]").toArray(new String[0])),
Arguments.of(RFC6265_STRICT, "Cookie: name=\"value;other=extra\"", 400, null, List.of("BadMessageException", "Bad Cookie quoted value").toArray(new String[0])),
Arguments.of(RFC6265_STRICT, "Cookie: name=\"value;other=extra\"", 400, null, List.of("400", "Bad Cookie quoted value").toArray(new String[0])),
Arguments.of(RFC6265, "Cookie: name=\"value;other=extra\"", 200, "name=value", null),
Arguments.of(RFC6265_LEGACY, "Cookie: name=\"value;other=extra\"", 200, null, List.of("[name=value;other=extra]").toArray(new String[0])),
Arguments.of(RFC2965, "Cookie: name=\"value;other=extra\"", 200, null, List.of("[name=value;other=extra]").toArray(new String[0])),

View File

@ -164,11 +164,7 @@ public class MultiPartServletTest
}
content.close();
assert400orEof(listener, responseContent ->
{
assertThat(responseContent, containsString("400: bad"));
assertThat(responseContent, containsString("Form is larger than max length"));
});
assert400orEof(listener, responseContent -> assertThat(responseContent, containsString("400")));
}
@ParameterizedTest
@ -203,11 +199,7 @@ public class MultiPartServletTest
.body(multiPart)
.send(listener);
assert400orEof(listener, responseContent ->
{
assertThat(responseContent, containsString("400: bad"));
assertThat(responseContent, containsString("Form with too many keys"));
});
assert400orEof(listener, responseContent -> assertThat(responseContent, containsString("400")));
}
@ParameterizedTest
@ -363,7 +355,6 @@ public class MultiPartServletTest
.send();
assertEquals(400, response.getStatus());
assertThat(response.getContentAsString(), containsString("max file size exceeded"));
}
String[] fileList = tmpDir.toFile().list();