Separate server properties for message and errors
Prior to this commit, there was a property server.error.include-details that allowed configuration of the message and errors attributes in a server error response. This commit separates the control of the message and errors attributes into two separate properties named server.error.include-message and server.error.include-binding-errors. When the message attribute is excluded from a servlet response, the value is changed from a hard-coded text value to an empty value. Fixes gh-20505
This commit is contained in:
@@ -46,9 +46,14 @@ public class ErrorProperties {
|
||||
private IncludeStacktrace includeStacktrace = IncludeStacktrace.NEVER;
|
||||
|
||||
/**
|
||||
* When to include "message" and "errors" attributes.
|
||||
* When to include "message" attribute.
|
||||
*/
|
||||
private IncludeDetails includeDetails = IncludeDetails.NEVER;
|
||||
private IncludeAttribute includeMessage = IncludeAttribute.NEVER;
|
||||
|
||||
/**
|
||||
* When to include "errors" attribute.
|
||||
*/
|
||||
private IncludeAttribute includeBindingErrors = IncludeAttribute.NEVER;
|
||||
|
||||
private final Whitelabel whitelabel = new Whitelabel();
|
||||
|
||||
@@ -76,12 +81,20 @@ public class ErrorProperties {
|
||||
this.includeStacktrace = includeStacktrace;
|
||||
}
|
||||
|
||||
public IncludeDetails getIncludeDetails() {
|
||||
return this.includeDetails;
|
||||
public IncludeAttribute getIncludeMessage() {
|
||||
return this.includeMessage;
|
||||
}
|
||||
|
||||
public void setIncludeDetails(IncludeDetails includeDetails) {
|
||||
this.includeDetails = includeDetails;
|
||||
public void setIncludeMessage(IncludeAttribute includeMessage) {
|
||||
this.includeMessage = includeMessage;
|
||||
}
|
||||
|
||||
public IncludeAttribute getIncludeBindingErrors() {
|
||||
return this.includeMessage;
|
||||
}
|
||||
|
||||
public void setIncludeBindingErrors(IncludeAttribute includeMessage) {
|
||||
this.includeMessage = includeMessage;
|
||||
}
|
||||
|
||||
public Whitelabel getWhitelabel() {
|
||||
@@ -96,39 +109,56 @@ public class ErrorProperties {
|
||||
/**
|
||||
* Never add stacktrace information.
|
||||
*/
|
||||
NEVER,
|
||||
NEVER(IncludeAttribute.NEVER),
|
||||
|
||||
/**
|
||||
* Always add stacktrace information.
|
||||
*/
|
||||
ALWAYS,
|
||||
ALWAYS(IncludeAttribute.ALWAYS),
|
||||
|
||||
/**
|
||||
* Add error attribute when the appropriate request parameter is "true".
|
||||
*/
|
||||
ON_PARAM(IncludeAttribute.ON_PARAM),
|
||||
|
||||
/**
|
||||
* Add stacktrace information when the "trace" request parameter is "true".
|
||||
* @deprecated since 2.3.0 in favor of {@link #ON_PARAM}
|
||||
*/
|
||||
ON_TRACE_PARAM
|
||||
@Deprecated
|
||||
ON_TRACE_PARAM(IncludeAttribute.ON_PARAM);
|
||||
|
||||
private final IncludeAttribute include;
|
||||
|
||||
IncludeStacktrace(IncludeAttribute include) {
|
||||
this.include = include;
|
||||
}
|
||||
|
||||
public IncludeAttribute getInclude() {
|
||||
return this.include;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Include error details attributes options.
|
||||
* Include error attributes options.
|
||||
*/
|
||||
public enum IncludeDetails {
|
||||
public enum IncludeAttribute {
|
||||
|
||||
/**
|
||||
* Never add error detail information.
|
||||
* Never add error attribute.
|
||||
*/
|
||||
NEVER,
|
||||
|
||||
/**
|
||||
* Always add error detail information.
|
||||
* Always add error attribute.
|
||||
*/
|
||||
ALWAYS,
|
||||
|
||||
/**
|
||||
* Add error details information when the "details" request parameter is "true".
|
||||
* Add error attribute when the appropriate request parameter is "true".
|
||||
*/
|
||||
ON_DETAILS_PARAM
|
||||
ON_PARAM
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -134,24 +134,26 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept
|
||||
* @param includeStackTrace whether to include the error stacktrace information
|
||||
* @return the error attributes as a Map
|
||||
* @deprecated since 2.3.0 in favor of
|
||||
* {@link #getErrorAttributes(ServerRequest, boolean, boolean)}
|
||||
* {@link #getErrorAttributes(ServerRequest, boolean, boolean, boolean)}
|
||||
*/
|
||||
@Deprecated
|
||||
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
|
||||
return this.errorAttributes.getErrorAttributes(request, includeStackTrace, false);
|
||||
return this.errorAttributes.getErrorAttributes(request, includeStackTrace, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the error attributes from the current request, to be used to populate error
|
||||
* views or JSON payloads.
|
||||
* @param request the source request
|
||||
* @param includeStackTrace whether to include the error stacktrace information
|
||||
* @param includeDetails whether to include message and errors attributes
|
||||
* @param includeStackTrace whether to include the stacktrace attribute
|
||||
* @param includeMessage whether to include the message attribute
|
||||
* @param includeBindingErrors whether to include the errors attribute
|
||||
* @return the error attributes as a Map
|
||||
*/
|
||||
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace,
|
||||
boolean includeDetails) {
|
||||
return this.errorAttributes.getErrorAttributes(request, includeStackTrace, includeDetails);
|
||||
boolean includeMessage, boolean includeBindingErrors) {
|
||||
return this.errorAttributes.getErrorAttributes(request, includeStackTrace, includeMessage,
|
||||
includeBindingErrors);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,13 +175,23 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the details attribute has been set on the given request.
|
||||
* Check whether the message attribute has been set on the given request.
|
||||
* @param request the source request
|
||||
* @return {@code true} if the error details have been requested, {@code false}
|
||||
* @return {@code true} if the message attribute has been requested, {@code false}
|
||||
* otherwise
|
||||
*/
|
||||
protected boolean isDetailsEnabled(ServerRequest request) {
|
||||
return getBooleanParameter(request, "details");
|
||||
protected boolean isMessageEnabled(ServerRequest request) {
|
||||
return getBooleanParameter(request, "message");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the errors attribute has been set on the given request.
|
||||
* @param request the source request
|
||||
* @return {@code true} if the errors attribute has been requested, {@code false}
|
||||
* otherwise
|
||||
*/
|
||||
protected boolean isBindingErrorsEnabled(ServerRequest request) {
|
||||
return getBooleanParameter(request, "errors");
|
||||
}
|
||||
|
||||
private boolean getBooleanParameter(ServerRequest request, String parameterName) {
|
||||
|
||||
@@ -114,8 +114,10 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa
|
||||
*/
|
||||
protected Mono<ServerResponse> renderErrorView(ServerRequest request) {
|
||||
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.TEXT_HTML);
|
||||
boolean includeDetails = isIncludeDetails(request, MediaType.TEXT_HTML);
|
||||
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeDetails);
|
||||
boolean includeMessage = isIncludeMessage(request, MediaType.TEXT_HTML);
|
||||
boolean includeBindingErrors = isIncludeBindingErrors(request, MediaType.TEXT_HTML);
|
||||
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeMessage,
|
||||
includeBindingErrors);
|
||||
int errorStatus = getHttpStatus(error);
|
||||
ServerResponse.BodyBuilder responseBody = ServerResponse.status(errorStatus).contentType(TEXT_HTML_UTF8);
|
||||
return Flux.just(getData(errorStatus).toArray(new String[] {}))
|
||||
@@ -143,8 +145,10 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa
|
||||
*/
|
||||
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
|
||||
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL);
|
||||
boolean includeDetails = isIncludeDetails(request, MediaType.ALL);
|
||||
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeDetails);
|
||||
boolean includeMessage = isIncludeMessage(request, MediaType.ALL);
|
||||
boolean includeBindingErrors = isIncludeBindingErrors(request, MediaType.ALL);
|
||||
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeMessage,
|
||||
includeBindingErrors);
|
||||
return ServerResponse.status(getHttpStatus(error)).contentType(MediaType.APPLICATION_JSON)
|
||||
.body(BodyInserters.fromValue(error));
|
||||
}
|
||||
@@ -155,10 +159,12 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa
|
||||
* @param produces the media type produced (or {@code MediaType.ALL})
|
||||
* @return if the stacktrace attribute should be included
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
protected boolean isIncludeStackTrace(ServerRequest request, MediaType produces) {
|
||||
switch (this.errorProperties.getIncludeStacktrace()) {
|
||||
case ALWAYS:
|
||||
return true;
|
||||
case ON_PARAM:
|
||||
case ON_TRACE_PARAM:
|
||||
return isTraceEnabled(request);
|
||||
default:
|
||||
@@ -167,17 +173,34 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the message and errors attributes should be included.
|
||||
* Determine if the message attribute should be included.
|
||||
* @param request the source request
|
||||
* @param produces the media type produced (or {@code MediaType.ALL})
|
||||
* @return if the message and errors attributes should be included
|
||||
* @return if the message attribute should be included
|
||||
*/
|
||||
protected boolean isIncludeDetails(ServerRequest request, MediaType produces) {
|
||||
switch (this.errorProperties.getIncludeDetails()) {
|
||||
protected boolean isIncludeMessage(ServerRequest request, MediaType produces) {
|
||||
switch (this.errorProperties.getIncludeMessage()) {
|
||||
case ALWAYS:
|
||||
return true;
|
||||
case ON_DETAILS_PARAM:
|
||||
return isDetailsEnabled(request);
|
||||
case ON_PARAM:
|
||||
return isMessageEnabled(request);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the errors attribute should be included.
|
||||
* @param request the source request
|
||||
* @param produces the media type produced (or {@code MediaType.ALL})
|
||||
* @return if the errors attribute should be included
|
||||
*/
|
||||
protected boolean isIncludeBindingErrors(ServerRequest request, MediaType produces) {
|
||||
switch (this.errorProperties.getIncludeBindingErrors()) {
|
||||
case ALWAYS:
|
||||
return true;
|
||||
case ON_PARAM:
|
||||
return isBindingErrorsEnabled(request);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -74,25 +74,30 @@ public abstract class AbstractErrorController implements ErrorController {
|
||||
* @param includeStackTrace if stack trace elements should be included
|
||||
* @return the error attributes
|
||||
* @deprecated since 2.3.0 in favor of
|
||||
* {@link #getErrorAttributes(HttpServletRequest, boolean, boolean)}
|
||||
* {@link #getErrorAttributes(HttpServletRequest, boolean, boolean, boolean)}
|
||||
*/
|
||||
@Deprecated
|
||||
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
|
||||
return getErrorAttributes(request, includeStackTrace, false);
|
||||
return getErrorAttributes(request, includeStackTrace, false, false);
|
||||
}
|
||||
|
||||
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace,
|
||||
boolean includeDetails) {
|
||||
boolean includeMessage, boolean includeBindingErrors) {
|
||||
WebRequest webRequest = new ServletWebRequest(request);
|
||||
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace, includeDetails);
|
||||
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace, includeMessage,
|
||||
includeBindingErrors);
|
||||
}
|
||||
|
||||
protected boolean getTraceParameter(HttpServletRequest request) {
|
||||
return getBooleanParameter(request, "trace");
|
||||
}
|
||||
|
||||
protected boolean getDetailsParameter(HttpServletRequest request) {
|
||||
return getBooleanParameter(request, "details");
|
||||
protected boolean getMessageParameter(HttpServletRequest request) {
|
||||
return getBooleanParameter(request, "message");
|
||||
}
|
||||
|
||||
protected boolean getErrorsParameter(HttpServletRequest request) {
|
||||
return getBooleanParameter(request, "errors");
|
||||
}
|
||||
|
||||
protected boolean getBooleanParameter(HttpServletRequest request, String parameterName) {
|
||||
|
||||
@@ -88,7 +88,8 @@ public class BasicErrorController extends AbstractErrorController {
|
||||
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
|
||||
HttpStatus status = getStatus(request);
|
||||
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(request,
|
||||
isIncludeStackTrace(request, MediaType.TEXT_HTML), isIncludeDetails(request, MediaType.TEXT_HTML)));
|
||||
isIncludeStackTrace(request, MediaType.TEXT_HTML), isIncludeMessage(request, MediaType.TEXT_HTML),
|
||||
isIncludeBindingErrors(request, MediaType.TEXT_HTML)));
|
||||
response.setStatus(status.value());
|
||||
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
|
||||
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
|
||||
@@ -101,7 +102,7 @@ public class BasicErrorController extends AbstractErrorController {
|
||||
return new ResponseEntity<>(status);
|
||||
}
|
||||
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL),
|
||||
isIncludeDetails(request, MediaType.ALL));
|
||||
isIncludeMessage(request, MediaType.ALL), isIncludeBindingErrors(request, MediaType.TEXT_HTML));
|
||||
return new ResponseEntity<>(body, status);
|
||||
}
|
||||
|
||||
@@ -117,10 +118,12 @@ public class BasicErrorController extends AbstractErrorController {
|
||||
* @param produces the media type produced (or {@code MediaType.ALL})
|
||||
* @return if the stacktrace attribute should be included
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) {
|
||||
switch (getErrorProperties().getIncludeStacktrace()) {
|
||||
case ALWAYS:
|
||||
return true;
|
||||
case ON_PARAM:
|
||||
case ON_TRACE_PARAM:
|
||||
return getTraceParameter(request);
|
||||
default:
|
||||
@@ -129,17 +132,34 @@ public class BasicErrorController extends AbstractErrorController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the error details attributes should be included.
|
||||
* Determine if the message attribute should be included.
|
||||
* @param request the source request
|
||||
* @param produces the media type produced (or {@code MediaType.ALL})
|
||||
* @return if the error details attributes should be included
|
||||
* @return if the message attribute should be included
|
||||
*/
|
||||
protected boolean isIncludeDetails(HttpServletRequest request, MediaType produces) {
|
||||
switch (getErrorProperties().getIncludeDetails()) {
|
||||
protected boolean isIncludeMessage(HttpServletRequest request, MediaType produces) {
|
||||
switch (getErrorProperties().getIncludeMessage()) {
|
||||
case ALWAYS:
|
||||
return true;
|
||||
case ON_DETAILS_PARAM:
|
||||
return getDetailsParameter(request);
|
||||
case ON_PARAM:
|
||||
return getMessageParameter(request);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the errors attribute should be included.
|
||||
* @param request the source request
|
||||
* @param produces the media type produced (or {@code MediaType.ALL})
|
||||
* @return if the errors attribute should be included
|
||||
*/
|
||||
protected boolean isIncludeBindingErrors(HttpServletRequest request, MediaType produces) {
|
||||
switch (getErrorProperties().getIncludeMessage()) {
|
||||
case ALWAYS:
|
||||
return true;
|
||||
case ON_PARAM:
|
||||
return getErrorsParameter(request);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,11 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "server.error.include-details",
|
||||
"name": "server.error.include-binding-errors",
|
||||
"defaultValue": "never"
|
||||
},
|
||||
{
|
||||
"name": "server.error.include-message",
|
||||
"defaultValue": "never"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -99,7 +99,7 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
|
||||
|
||||
@Test
|
||||
void htmlError() {
|
||||
this.contextRunner.withPropertyValues("server.error.include-details=always").run((context) -> {
|
||||
this.contextRunner.withPropertyValues("server.error.include-message=always").run((context) -> {
|
||||
WebTestClient client = getWebClient(context);
|
||||
String body = client.get().uri("/").accept(MediaType.TEXT_HTML).exchange().expectStatus()
|
||||
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectHeader().contentType(TEXT_HTML_UTF8)
|
||||
@@ -116,26 +116,43 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
|
||||
.isBadRequest().expectBody().jsonPath("status").isEqualTo("400").jsonPath("error")
|
||||
.isEqualTo(HttpStatus.BAD_REQUEST.getReasonPhrase()).jsonPath("path").isEqualTo(("/bind"))
|
||||
.jsonPath("exception").doesNotExist().jsonPath("errors").doesNotExist().jsonPath("message")
|
||||
.isNotEmpty().jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId());
|
||||
.isEmpty().jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindingResultErrorIncludeDetails() {
|
||||
this.contextRunner.withPropertyValues("server.error.include-details=on-details-param").run((context) -> {
|
||||
WebTestClient client = getWebClient(context);
|
||||
client.post().uri("/bind?details=true").contentType(MediaType.APPLICATION_JSON).bodyValue("{}").exchange()
|
||||
.expectStatus().isBadRequest().expectBody().jsonPath("status").isEqualTo("400").jsonPath("error")
|
||||
.isEqualTo(HttpStatus.BAD_REQUEST.getReasonPhrase()).jsonPath("path").isEqualTo(("/bind"))
|
||||
.jsonPath("exception").doesNotExist().jsonPath("errors").isArray().jsonPath("message").isNotEmpty()
|
||||
.jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId());
|
||||
});
|
||||
void bindingResultErrorIncludeMessageAndErrors() {
|
||||
this.contextRunner.withPropertyValues("server.error.include-message=on-param",
|
||||
"server.error.include-binding-errors=on-param").run((context) -> {
|
||||
WebTestClient client = getWebClient(context);
|
||||
client.post().uri("/bind?message=true&errors=true").contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue("{}").exchange().expectStatus().isBadRequest().expectBody().jsonPath("status")
|
||||
.isEqualTo("400").jsonPath("error").isEqualTo(HttpStatus.BAD_REQUEST.getReasonPhrase())
|
||||
.jsonPath("path").isEqualTo(("/bind")).jsonPath("exception").doesNotExist()
|
||||
.jsonPath("errors").isArray().jsonPath("message").isNotEmpty().jsonPath("requestId")
|
||||
.isEqualTo(this.logIdFilter.getLogId());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void includeStackTraceOnTraceParam() {
|
||||
this.contextRunner.withPropertyValues("server.error.include-exception=true",
|
||||
"server.error.include-stacktrace=on-trace-param").run((context) -> {
|
||||
WebTestClient client = getWebClient(context);
|
||||
client.get().uri("/?trace=true").exchange().expectStatus()
|
||||
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody().jsonPath("status")
|
||||
.isEqualTo("500").jsonPath("error")
|
||||
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()).jsonPath("exception")
|
||||
.isEqualTo(IllegalStateException.class.getName()).jsonPath("trace").exists()
|
||||
.jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void includeStackTraceOnParam() {
|
||||
this.contextRunner.withPropertyValues("server.error.include-exception=true",
|
||||
"server.error.include-stacktrace=on-trace-param").run((context) -> {
|
||||
this.contextRunner
|
||||
.withPropertyValues("server.error.include-exception=true", "server.error.include-stacktrace=on-param")
|
||||
.run((context) -> {
|
||||
WebTestClient client = getWebClient(context);
|
||||
client.get().uri("/?trace=true").exchange().expectStatus()
|
||||
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody().jsonPath("status")
|
||||
@@ -177,11 +194,12 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void includeDetailsOnParam() {
|
||||
this.contextRunner.withPropertyValues("server.error.include-exception=true",
|
||||
"server.error.include-details=on-details-param").run((context) -> {
|
||||
void includeMessageOnParam() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("server.error.include-exception=true", "server.error.include-message=on-param")
|
||||
.run((context) -> {
|
||||
WebTestClient client = getWebClient(context);
|
||||
client.get().uri("/?details=true").exchange().expectStatus()
|
||||
client.get().uri("/?message=true").exchange().expectStatus()
|
||||
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody().jsonPath("status")
|
||||
.isEqualTo("500").jsonPath("error")
|
||||
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()).jsonPath("exception")
|
||||
@@ -191,9 +209,9 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void alwaysIncludeDetails() {
|
||||
void alwaysIncludeMessage() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("server.error.include-exception=true", "server.error.include-details=always")
|
||||
.withPropertyValues("server.error.include-exception=true", "server.error.include-message=always")
|
||||
.run((context) -> {
|
||||
WebTestClient client = getWebClient(context);
|
||||
client.get().uri("/?trace=false").exchange().expectStatus()
|
||||
@@ -206,9 +224,9 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void neverIncludeDetails() {
|
||||
void neverIncludeMessage() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("server.error.include-exception=true", "server.error.include-details=never")
|
||||
.withPropertyValues("server.error.include-exception=true", "server.error.include-message=never")
|
||||
.run((context) -> {
|
||||
WebTestClient client = getWebClient(context);
|
||||
client.get().uri("/?trace=true").exchange().expectStatus()
|
||||
@@ -235,7 +253,7 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
|
||||
void defaultErrorView() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.mustache.prefix=classpath:/unknown/",
|
||||
"server.error.include-stacktrace=always", "server.error.include-details=always")
|
||||
"server.error.include-stacktrace=always", "server.error.include-message=always")
|
||||
.run((context) -> {
|
||||
WebTestClient client = getWebClient(context);
|
||||
String body = client.get().uri("/").accept(MediaType.TEXT_HTML).exchange().expectStatus()
|
||||
@@ -250,7 +268,7 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
|
||||
@Test
|
||||
void escapeHtmlInDefaultErrorView() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.mustache.prefix=classpath:/unknown/", "server.error.include-details=always")
|
||||
.withPropertyValues("spring.mustache.prefix=classpath:/unknown/", "server.error.include-message=always")
|
||||
.run((context) -> {
|
||||
WebTestClient client = getWebClient(context);
|
||||
String body = client.get().uri("/html").accept(MediaType.TEXT_HTML).exchange().expectStatus()
|
||||
|
||||
@@ -66,7 +66,8 @@ class DefaultErrorWebExceptionHandlerTests {
|
||||
ResourceProperties resourceProperties = new ResourceProperties();
|
||||
ErrorProperties errorProperties = new ErrorProperties();
|
||||
ApplicationContext context = new AnnotationConfigReactiveWebApplicationContext();
|
||||
given(errorAttributes.getErrorAttributes(any(), anyBoolean(), anyBoolean())).willReturn(getErrorAttributes());
|
||||
given(errorAttributes.getErrorAttributes(any(), anyBoolean(), anyBoolean(), anyBoolean()))
|
||||
.willReturn(getErrorAttributes());
|
||||
DefaultErrorWebExceptionHandler exceptionHandler = new DefaultErrorWebExceptionHandler(errorAttributes,
|
||||
resourceProperties, errorProperties, context);
|
||||
setupViewResolver(exceptionHandler);
|
||||
|
||||
@@ -88,57 +88,63 @@ class BasicErrorControllerIntegrationTests {
|
||||
void testErrorForMachineClientDefault() {
|
||||
load();
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("?trace=true"), Map.class);
|
||||
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", null,
|
||||
"An error occurred while processing the request", "/");
|
||||
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", null, "", "/");
|
||||
assertThat(entity.getBody().containsKey("exception")).isFalse();
|
||||
assertThat(entity.getBody().containsKey("trace")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testErrorForMachineClientWithParamsTrue() {
|
||||
void testErrorForMachineClientWithTraceParamsTrue() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-trace-param",
|
||||
"--server.error.include-details=on-details-param");
|
||||
exceptionWithStackTraceAndDetails("?trace=true&details=true");
|
||||
"--server.error.include-message=on-param");
|
||||
exceptionWithStackTraceAndMessage("?trace=true&message=true");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testErrorForMachineClientWithParamsTrue() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-param",
|
||||
"--server.error.include-message=on-param");
|
||||
exceptionWithStackTraceAndMessage("?trace=true&message=true");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testErrorForMachineClientWithParamsFalse() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-trace-param",
|
||||
"--server.error.include-details=on-details-param");
|
||||
exceptionWithoutStackTraceAndDetails("?trace=false&details=false");
|
||||
load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-param",
|
||||
"--server.error.include-message=on-param");
|
||||
exceptionWithoutStackTraceAndMessage("?trace=false&message=false");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testErrorForMachineClientWithParamsAbsent() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-trace-param",
|
||||
"--server.error.include-details=on-details-param");
|
||||
exceptionWithoutStackTraceAndDetails("");
|
||||
load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-param",
|
||||
"--server.error.include-message=on-param");
|
||||
exceptionWithoutStackTraceAndMessage("");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testErrorForMachineClientNeverParams() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-stacktrace=never",
|
||||
"--server.error.include-details=never");
|
||||
exceptionWithoutStackTraceAndDetails("?trace=true&details=true");
|
||||
"--server.error.include-message=never");
|
||||
exceptionWithoutStackTraceAndMessage("?trace=true&message=true");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testErrorForMachineClientAlwaysParams() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-stacktrace=always",
|
||||
"--server.error.include-details=always");
|
||||
exceptionWithStackTraceAndDetails("?trace=false&details=false");
|
||||
"--server.error.include-message=always");
|
||||
exceptionWithStackTraceAndMessage("?trace=false&message=false");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testErrorForMachineClientAlwaysParamsWithoutMessage() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-details=always");
|
||||
load("--server.error.include-exception=true", "--server.error.include-message=always");
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/noMessage"), Map.class);
|
||||
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class,
|
||||
"No message available", "/noMessage");
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void exceptionWithStackTraceAndDetails(String path) {
|
||||
private void exceptionWithStackTraceAndMessage(String path) {
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl(path), Map.class);
|
||||
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class,
|
||||
"Expected!", "/");
|
||||
@@ -146,26 +152,25 @@ class BasicErrorControllerIntegrationTests {
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void exceptionWithoutStackTraceAndDetails(String path) {
|
||||
private void exceptionWithoutStackTraceAndMessage(String path) {
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl(path), Map.class);
|
||||
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class,
|
||||
"An error occurred while processing the request", "/");
|
||||
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class, "", "/");
|
||||
assertThat(entity.getBody().containsKey("trace")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
void testErrorForAnnotatedExceptionWithoutDetails() {
|
||||
void testErrorForAnnotatedExceptionWithoutMessage() {
|
||||
load("--server.error.include-exception=true");
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotated"), Map.class);
|
||||
assertErrorAttributes(entity.getBody(), "400", "Bad Request", TestConfiguration.Errors.ExpectedException.class,
|
||||
"An error occurred while processing the request", "/annotated");
|
||||
"", "/annotated");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
void testErrorForAnnotatedExceptionWithDetails() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-details=always");
|
||||
void testErrorForAnnotatedExceptionWithMessage() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-message=always");
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotated"), Map.class);
|
||||
assertErrorAttributes(entity.getBody(), "400", "Bad Request", TestConfiguration.Errors.ExpectedException.class,
|
||||
"Expected!", "/annotated");
|
||||
@@ -173,18 +178,17 @@ class BasicErrorControllerIntegrationTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
void testErrorForAnnotatedNoReasonExceptionWithoutDetails() {
|
||||
void testErrorForAnnotatedNoReasonExceptionWithoutMessage() {
|
||||
load("--server.error.include-exception=true");
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotatedNoReason"), Map.class);
|
||||
assertErrorAttributes(entity.getBody(), "406", "Not Acceptable",
|
||||
TestConfiguration.Errors.NoReasonExpectedException.class,
|
||||
"An error occurred while processing the request", "/annotatedNoReason");
|
||||
TestConfiguration.Errors.NoReasonExpectedException.class, "", "/annotatedNoReason");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
void testErrorForAnnotatedNoReasonExceptionWithDetails() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-details=always");
|
||||
void testErrorForAnnotatedNoReasonExceptionWithMessage() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-message=always");
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotatedNoReason"), Map.class);
|
||||
assertErrorAttributes(entity.getBody(), "406", "Not Acceptable",
|
||||
TestConfiguration.Errors.NoReasonExpectedException.class, "Expected message", "/annotatedNoReason");
|
||||
@@ -192,8 +196,8 @@ class BasicErrorControllerIntegrationTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
void testErrorForAnnotatedNoMessageExceptionWithDetails() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-details=always");
|
||||
void testErrorForAnnotatedNoMessageExceptionWithMessage() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-message=always");
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotatedNoMessage"), Map.class);
|
||||
assertErrorAttributes(entity.getBody(), "406", "Not Acceptable",
|
||||
TestConfiguration.Errors.NoReasonExpectedException.class, "No message available",
|
||||
@@ -201,37 +205,42 @@ class BasicErrorControllerIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingExceptionForMachineClientWithDetailsParamTrue() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-details=on-details-param");
|
||||
bindingExceptionWithDetails("?details=true");
|
||||
void testBindingExceptionForMachineClientWithMessageAndErrorsParamTrue() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-message=on-param",
|
||||
"--server.error.include-binding-errors=on-param");
|
||||
bindingExceptionWithMessageAndErrors("?message=true&errors=true");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingExceptionForMachineClientWithDetailsParamFalse() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-details=on-details-param");
|
||||
bindingExceptionWithoutDetails("?details=false");
|
||||
void testBindingExceptionForMachineClientWithMessageAndErrorsParamFalse() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-message=on-param",
|
||||
"--server.error.include-binding-errors=on-param");
|
||||
bindingExceptionWithoutMessageAndErrors("?message=false&errors=false");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingExceptionForMachineClientWithDetailsParamAbsent() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-details=on-details-param");
|
||||
bindingExceptionWithoutDetails("");
|
||||
void testBindingExceptionForMachineClientWithMessageAndErrorsParamAbsent() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-message=on-param",
|
||||
"--server.error.include-binding-errors=on-param");
|
||||
bindingExceptionWithoutMessageAndErrors("");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingExceptionForMachineClientAlwaysDetails() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-details=always");
|
||||
bindingExceptionWithDetails("?details=false");
|
||||
void testBindingExceptionForMachineClientAlwaysMessageAndErrors() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-message=always",
|
||||
"--server.error.include-binding-errors=always");
|
||||
bindingExceptionWithMessageAndErrors("?message=false&errors=false");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingExceptionForMachineClientNeverDetails() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-details=never");
|
||||
bindingExceptionWithoutDetails("?details=true");
|
||||
void testBindingExceptionForMachineClientNeverMessageAndErrors() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-message=never",
|
||||
"--server.error.include-binding-errors=never");
|
||||
bindingExceptionWithoutMessageAndErrors("?message=true&errors=true");
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes" })
|
||||
private void bindingExceptionWithDetails(String param) {
|
||||
private void bindingExceptionWithMessageAndErrors(String param) {
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/bind" + param), Map.class);
|
||||
assertErrorAttributes(entity.getBody(), "400", "Bad Request", BindException.class,
|
||||
"Validation failed for object='test'. Error count: 1", "/bind");
|
||||
@@ -239,10 +248,9 @@ class BasicErrorControllerIntegrationTests {
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes" })
|
||||
private void bindingExceptionWithoutDetails(String param) {
|
||||
private void bindingExceptionWithoutMessageAndErrors(String param) {
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/bind" + param), Map.class);
|
||||
assertErrorAttributes(entity.getBody(), "400", "Bad Request", BindException.class, "Validation failed",
|
||||
"/bind");
|
||||
assertErrorAttributes(entity.getBody(), "400", "Bad Request", BindException.class, "", "/bind");
|
||||
assertThat(entity.getBody().containsKey("errors")).isFalse();
|
||||
}
|
||||
|
||||
@@ -253,8 +261,8 @@ class BasicErrorControllerIntegrationTests {
|
||||
RequestEntity request = RequestEntity.post(URI.create(createUrl("/bodyValidation")))
|
||||
.accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON).body("{}");
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().exchange(request, Map.class);
|
||||
assertErrorAttributes(entity.getBody(), "400", "Bad Request", MethodArgumentNotValidException.class,
|
||||
"Validation failed", "/bodyValidation");
|
||||
assertErrorAttributes(entity.getBody(), "400", "Bad Request", MethodArgumentNotValidException.class, "",
|
||||
"/bodyValidation");
|
||||
assertThat(entity.getBody().containsKey("errors")).isFalse();
|
||||
}
|
||||
|
||||
@@ -262,7 +270,7 @@ class BasicErrorControllerIntegrationTests {
|
||||
@SuppressWarnings("rawtypes")
|
||||
void testBindingExceptionForMachineClientDefault() {
|
||||
load();
|
||||
RequestEntity request = RequestEntity.get(URI.create(createUrl("/bind?trace=true,details=true")))
|
||||
RequestEntity request = RequestEntity.get(URI.create(createUrl("/bind?trace=true,message=true")))
|
||||
.accept(MediaType.APPLICATION_JSON).build();
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().exchange(request, Map.class);
|
||||
assertThat(entity.getBody().containsKey("exception")).isFalse();
|
||||
|
||||
@@ -69,7 +69,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
* @author Dave Syer
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
@SpringBootTest(properties = { "server.error.include-details=always" })
|
||||
@SpringBootTest(properties = { "server.error.include-message=always" })
|
||||
@DirtiesContext
|
||||
class BasicErrorControllerMockMvcTests {
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
* @author Dave Syer
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
@SpringBootTest(properties = { "server.error.include-details=always" })
|
||||
@SpringBootTest(properties = { "server.error.include-message=always" })
|
||||
@DirtiesContext
|
||||
class DefaultErrorViewIntegrationTests {
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ class ErrorMvcAutoConfigurationTests {
|
||||
ErrorAttributes errorAttributes = context.getBean(ErrorAttributes.class);
|
||||
DispatcherServletWebRequest webRequest = createWebRequest(new IllegalStateException("Exception message"),
|
||||
false);
|
||||
errorView.render(errorAttributes.getErrorAttributes(webRequest, true, true), webRequest.getRequest(),
|
||||
errorView.render(errorAttributes.getErrorAttributes(webRequest, true, true, true), webRequest.getRequest(),
|
||||
webRequest.getResponse());
|
||||
assertThat(webRequest.getResponse().getContentType()).isEqualTo("text/html;charset=UTF-8");
|
||||
String responseString = ((MockHttpServletResponse) webRequest.getResponse()).getContentAsString();
|
||||
@@ -69,7 +69,7 @@ class ErrorMvcAutoConfigurationTests {
|
||||
ErrorAttributes errorAttributes = context.getBean(ErrorAttributes.class);
|
||||
DispatcherServletWebRequest webRequest = createWebRequest(new IllegalStateException("Exception message"),
|
||||
true);
|
||||
errorView.render(errorAttributes.getErrorAttributes(webRequest, true, true), webRequest.getRequest(),
|
||||
errorView.render(errorAttributes.getErrorAttributes(webRequest, true, true, true), webRequest.getRequest(),
|
||||
webRequest.getResponse());
|
||||
assertThat(output).contains("Cannot render error page for request [/path] "
|
||||
+ "and exception [Exception message] as the response has "
|
||||
|
||||
Reference in New Issue
Block a user