Disable exception details on default error views
Prior to this commit, default error responses included the message from a handled exception. When the exception was a BindException, the error responses could also include an errors attribute containing the details of the binding failure. These details could leak information about the application. This commit removes the exception message and binding errors detail from error responses by default, and introduces a `server.error.include-details` property that can be used to cause these details to be included in the response. Fixes gh-20505
This commit is contained in:
parent
866147405c
commit
70d4994502
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -47,7 +47,7 @@ public class ManagementErrorEndpoint {
|
|||
@RequestMapping("${server.error.path:${error.path:/error}}")
|
||||
@ResponseBody
|
||||
public Map<String, Object> invoke(ServletWebRequest request) {
|
||||
return this.errorAttributes.getErrorAttributes(request, false);
|
||||
return this.errorAttributes.getErrorAttributes(request, false, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
* Integration tests for {@link WebMvcEndpointChildContextConfiguration}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class WebMvcEndpointChildContextConfigurationIntegrationTests {
|
||||
|
||||
|
@ -59,7 +60,8 @@ class WebMvcEndpointChildContextConfigurationIntegrationTests {
|
|||
WebClient client = WebClient.create("http://localhost:" + port);
|
||||
ClientResponse response = client.get().uri("actuator/fail").accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().block();
|
||||
assertThat(response.bodyToMono(String.class).block()).contains("message\":\"Epic Fail");
|
||||
assertThat(response.bodyToMono(String.class).block())
|
||||
.contains("message\":\"An error occurred while processing the request");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -61,6 +61,7 @@ import static org.mockito.Mockito.verify;
|
|||
*
|
||||
* @param <T> the type of application context used by the tests
|
||||
* @author Andy Wilkinson
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
public abstract class AbstractWebEndpointIntegrationTests<T extends ConfigurableApplicationContext & AnnotationConfigRegistry> {
|
||||
|
||||
|
@ -409,8 +410,10 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
|||
BiConsumer<ApplicationContext, WebTestClient> consumer) {
|
||||
T applicationContext = this.applicationContextSupplier.get();
|
||||
contextCustomizer.accept(applicationContext);
|
||||
applicationContext.getEnvironment().getPropertySources()
|
||||
.addLast(new MapPropertySource("test", Collections.singletonMap("endpointPath", endpointPath)));
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put("endpointPath", endpointPath);
|
||||
properties.put("server.error.include-details", "always");
|
||||
applicationContext.getEnvironment().getPropertySources().addLast(new MapPropertySource("test", properties));
|
||||
applicationContext.refresh();
|
||||
try {
|
||||
InetSocketAddress address = new InetSocketAddress(getPort(applicationContext));
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Value;
|
|||
* @author Michael Stummvoll
|
||||
* @author Stephane Nicoll
|
||||
* @author Vedran Pavic
|
||||
* @author Scott Frederick
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class ErrorProperties {
|
||||
|
@ -40,10 +41,15 @@ public class ErrorProperties {
|
|||
private boolean includeException;
|
||||
|
||||
/**
|
||||
* When to include a "stacktrace" attribute.
|
||||
* When to include the "trace" attribute.
|
||||
*/
|
||||
private IncludeStacktrace includeStacktrace = IncludeStacktrace.NEVER;
|
||||
|
||||
/**
|
||||
* When to include "message" and "errors" attributes.
|
||||
*/
|
||||
private IncludeDetails includeDetails = IncludeDetails.NEVER;
|
||||
|
||||
private final Whitelabel whitelabel = new Whitelabel();
|
||||
|
||||
public String getPath() {
|
||||
|
@ -70,6 +76,14 @@ public class ErrorProperties {
|
|||
this.includeStacktrace = includeStacktrace;
|
||||
}
|
||||
|
||||
public IncludeDetails getIncludeDetails() {
|
||||
return this.includeDetails;
|
||||
}
|
||||
|
||||
public void setIncludeDetails(IncludeDetails includeDetails) {
|
||||
this.includeDetails = includeDetails;
|
||||
}
|
||||
|
||||
public Whitelabel getWhitelabel() {
|
||||
return this.whitelabel;
|
||||
}
|
||||
|
@ -96,6 +110,28 @@ public class ErrorProperties {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Include error details attributes options.
|
||||
*/
|
||||
public enum IncludeDetails {
|
||||
|
||||
/**
|
||||
* Never add error detail information.
|
||||
*/
|
||||
NEVER,
|
||||
|
||||
/**
|
||||
* Always add error detail information.
|
||||
*/
|
||||
ALWAYS,
|
||||
|
||||
/**
|
||||
* Add error details information when the "details" request parameter is "true".
|
||||
*/
|
||||
ON_DETAILS_PARAM
|
||||
|
||||
}
|
||||
|
||||
public static class Whitelabel {
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -54,6 +54,7 @@ import org.springframework.web.util.HtmlUtils;
|
|||
* Abstract base class for {@link ErrorWebExceptionHandler} implementations.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Scott Frederick
|
||||
* @since 2.0.0
|
||||
* @see ErrorAttributes
|
||||
*/
|
||||
|
@ -131,10 +132,26 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept
|
|||
* views or JSON payloads.
|
||||
* @param request the source request
|
||||
* @param includeStackTrace whether to include the error stacktrace information
|
||||
* @return the error attributes as a Map.
|
||||
* @return the error attributes as a Map
|
||||
* @deprecated since 2.3.0 in favor of
|
||||
* {@link #getErrorAttributes(ServerRequest, boolean, boolean)}
|
||||
*/
|
||||
@Deprecated
|
||||
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
|
||||
return this.errorAttributes.getErrorAttributes(request, includeStackTrace);
|
||||
return this.errorAttributes.getErrorAttributes(request, includeStackTrace, 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
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -152,7 +169,21 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept
|
|||
* @return {@code true} if the error trace has been requested, {@code false} otherwise
|
||||
*/
|
||||
protected boolean isTraceEnabled(ServerRequest request) {
|
||||
String parameter = request.queryParam("trace").orElse("false");
|
||||
return getBooleanParameter(request, "trace");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the details 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}
|
||||
* otherwise
|
||||
*/
|
||||
protected boolean isDetailsEnabled(ServerRequest request) {
|
||||
return getBooleanParameter(request, "details");
|
||||
}
|
||||
|
||||
private boolean getBooleanParameter(ServerRequest request, String parameterName) {
|
||||
String parameter = request.queryParam(parameterName).orElse("false");
|
||||
return !"false".equalsIgnoreCase(parameter);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -71,6 +71,7 @@ import static org.springframework.web.reactive.function.server.RouterFunctions.r
|
|||
* payload.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Scott Frederick
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
|
||||
|
@ -113,7 +114,8 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa
|
|||
*/
|
||||
protected Mono<ServerResponse> renderErrorView(ServerRequest request) {
|
||||
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.TEXT_HTML);
|
||||
Map<String, Object> error = getErrorAttributes(request, includeStackTrace);
|
||||
boolean includeDetails = isIncludeDetails(request, MediaType.TEXT_HTML);
|
||||
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeDetails);
|
||||
int errorStatus = getHttpStatus(error);
|
||||
ServerResponse.BodyBuilder responseBody = ServerResponse.status(errorStatus).contentType(TEXT_HTML_UTF8);
|
||||
return Flux.just(getData(errorStatus).toArray(new String[] {}))
|
||||
|
@ -141,7 +143,8 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa
|
|||
*/
|
||||
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
|
||||
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL);
|
||||
Map<String, Object> error = getErrorAttributes(request, includeStackTrace);
|
||||
boolean includeDetails = isIncludeDetails(request, MediaType.ALL);
|
||||
Map<String, Object> error = getErrorAttributes(request, includeStackTrace, includeDetails);
|
||||
return ServerResponse.status(getHttpStatus(error)).contentType(MediaType.APPLICATION_JSON)
|
||||
.body(BodyInserters.fromValue(error));
|
||||
}
|
||||
|
@ -163,6 +166,23 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the message and errors attributes 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
|
||||
*/
|
||||
protected boolean isIncludeDetails(ServerRequest request, MediaType produces) {
|
||||
ErrorProperties.IncludeDetails include = this.errorProperties.getIncludeDetails();
|
||||
if (include == ErrorProperties.IncludeDetails.ALWAYS) {
|
||||
return true;
|
||||
}
|
||||
if (include == ErrorProperties.IncludeDetails.ON_DETAILS_PARAM) {
|
||||
return isDetailsEnabled(request);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTTP error status information from the error map.
|
||||
* @param errorAttributes the current error information
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -38,6 +38,7 @@ import org.springframework.web.servlet.ModelAndView;
|
|||
*
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
* @since 1.3.0
|
||||
* @see ErrorAttributes
|
||||
*/
|
||||
|
@ -66,13 +67,35 @@ public abstract class AbstractErrorController implements ErrorController {
|
|||
return sorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Map} of the error attributes.
|
||||
* @param request the source request
|
||||
* @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)}
|
||||
*/
|
||||
@Deprecated
|
||||
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
|
||||
return this.getErrorAttributes(request, includeStackTrace, false);
|
||||
}
|
||||
|
||||
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace,
|
||||
boolean includeDetails) {
|
||||
WebRequest webRequest = new ServletWebRequest(request);
|
||||
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
|
||||
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace, includeDetails);
|
||||
}
|
||||
|
||||
protected boolean getTraceParameter(HttpServletRequest request) {
|
||||
String parameter = request.getParameter("trace");
|
||||
return getBooleanParameter(request, "trace");
|
||||
}
|
||||
|
||||
protected boolean getDetailsParameter(HttpServletRequest request) {
|
||||
return getBooleanParameter(request, "details");
|
||||
}
|
||||
|
||||
protected boolean getBooleanParameter(HttpServletRequest request, String parameterName) {
|
||||
String parameter = request.getParameter(parameterName);
|
||||
if (parameter == null) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.boot.autoconfigure.web.ErrorProperties;
|
||||
import org.springframework.boot.autoconfigure.web.ErrorProperties.IncludeDetails;
|
||||
import org.springframework.boot.autoconfigure.web.ErrorProperties.IncludeStacktrace;
|
||||
import org.springframework.boot.web.servlet.error.ErrorAttributes;
|
||||
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory;
|
||||
|
@ -47,6 +48,7 @@ import org.springframework.web.servlet.ModelAndView;
|
|||
* @author Phillip Webb
|
||||
* @author Michael Stummvoll
|
||||
* @author Stephane Nicoll
|
||||
* @author Scott Frederick
|
||||
* @since 1.0.0
|
||||
* @see ErrorAttributes
|
||||
* @see ErrorProperties
|
||||
|
@ -87,8 +89,8 @@ public class BasicErrorController extends AbstractErrorController {
|
|||
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
|
||||
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
|
||||
HttpStatus status = getStatus(request);
|
||||
Map<String, Object> model = Collections
|
||||
.unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
|
||||
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(request,
|
||||
isIncludeStackTrace(request, MediaType.TEXT_HTML), isIncludeDetails(request, MediaType.TEXT_HTML)));
|
||||
response.setStatus(status.value());
|
||||
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
|
||||
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
|
||||
|
@ -100,7 +102,8 @@ public class BasicErrorController extends AbstractErrorController {
|
|||
if (status == HttpStatus.NO_CONTENT) {
|
||||
return new ResponseEntity<>(status);
|
||||
}
|
||||
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
|
||||
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL),
|
||||
isIncludeDetails(request, MediaType.ALL));
|
||||
return new ResponseEntity<>(body, status);
|
||||
}
|
||||
|
||||
|
@ -127,6 +130,23 @@ public class BasicErrorController extends AbstractErrorController {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the error details attributes 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
|
||||
*/
|
||||
protected boolean isIncludeDetails(HttpServletRequest request, MediaType produces) {
|
||||
IncludeDetails include = getErrorProperties().getIncludeDetails();
|
||||
if (include == IncludeDetails.ALWAYS) {
|
||||
return true;
|
||||
}
|
||||
if (include == IncludeDetails.ON_DETAILS_PARAM) {
|
||||
return getDetailsParameter(request);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide access to the error properties.
|
||||
* @return the error properties
|
||||
|
|
|
@ -106,6 +106,10 @@
|
|||
"description": "Minimum \"Content-Length\" value that is required for compression to be performed.",
|
||||
"defaultValue": "2KB"
|
||||
},
|
||||
{
|
||||
"name": "server.error.include-details",
|
||||
"defaultValue": "never"
|
||||
},
|
||||
{
|
||||
"name": "server.error.include-stacktrace",
|
||||
"defaultValue": "never"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -55,6 +55,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
|||
* Integration tests for {@link DefaultErrorWebExceptionHandler}
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class DefaultErrorWebExceptionHandlerIntegrationTests {
|
||||
|
@ -78,8 +79,8 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
|
|||
client.get().uri("/").exchange().expectStatus().isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody()
|
||||
.jsonPath("status").isEqualTo("500").jsonPath("error")
|
||||
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()).jsonPath("path").isEqualTo(("/"))
|
||||
.jsonPath("message").isEqualTo("Expected!").jsonPath("exception").doesNotExist().jsonPath("trace")
|
||||
.doesNotExist().jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId());
|
||||
.jsonPath("message").isEmpty().jsonPath("exception").doesNotExist().jsonPath("trace").doesNotExist()
|
||||
.jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId());
|
||||
assertThat(output).contains("500 Server Error for HTTP GET \"/\"")
|
||||
.contains("java.lang.IllegalStateException: Expected!");
|
||||
});
|
||||
|
@ -98,7 +99,7 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
|
|||
|
||||
@Test
|
||||
void htmlError() {
|
||||
this.contextRunner.run((context) -> {
|
||||
this.contextRunner.withPropertyValues("server.error.include-details=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)
|
||||
|
@ -114,6 +115,18 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
|
|||
client.post().uri("/bind").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").doesNotExist().jsonPath("message")
|
||||
.isNotEmpty().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());
|
||||
});
|
||||
|
@ -163,6 +176,50 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
|
|||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void includeDetailsOnParam() {
|
||||
this.contextRunner.withPropertyValues("server.error.include-exception=true",
|
||||
"server.error.include-details=on-details-param").run((context) -> {
|
||||
WebTestClient client = getWebClient(context);
|
||||
client.get().uri("/?details=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("message").isNotEmpty()
|
||||
.jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void alwaysIncludeDetails() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("server.error.include-exception=true", "server.error.include-details=always")
|
||||
.run((context) -> {
|
||||
WebTestClient client = getWebClient(context);
|
||||
client.get().uri("/?trace=false").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("message").isNotEmpty()
|
||||
.jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void neverIncludeDetails() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("server.error.include-exception=true", "server.error.include-details=never")
|
||||
.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("message").isEmpty()
|
||||
.jsonPath("requestId").isEqualTo(this.logIdFilter.getLogId());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void statusException() {
|
||||
this.contextRunner.withPropertyValues("server.error.include-exception=true").run((context) -> {
|
||||
|
@ -176,8 +233,10 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
|
|||
|
||||
@Test
|
||||
void defaultErrorView() {
|
||||
this.contextRunner.withPropertyValues("spring.mustache.prefix=classpath:/unknown/",
|
||||
"server.error.include-stacktrace=always").run((context) -> {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.mustache.prefix=classpath:/unknown/",
|
||||
"server.error.include-stacktrace=always", "server.error.include-details=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)
|
||||
|
@ -190,7 +249,9 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
|
|||
|
||||
@Test
|
||||
void escapeHtmlInDefaultErrorView() {
|
||||
this.contextRunner.withPropertyValues("spring.mustache.prefix=classpath:/unknown/").run((context) -> {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.mustache.prefix=classpath:/unknown/", "server.error.include-details=always")
|
||||
.run((context) -> {
|
||||
WebTestClient client = getWebClient(context);
|
||||
String body = client.get().uri("/html").accept(MediaType.TEXT_HTML).exchange().expectStatus()
|
||||
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectHeader().contentType(TEXT_HTML_UTF8)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -47,6 +47,7 @@ import static org.mockito.Mockito.mock;
|
|||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class DefaultErrorWebExceptionHandlerTests {
|
||||
|
||||
|
@ -65,7 +66,7 @@ class DefaultErrorWebExceptionHandlerTests {
|
|||
ResourceProperties resourceProperties = new ResourceProperties();
|
||||
ErrorProperties errorProperties = new ErrorProperties();
|
||||
ApplicationContext context = new AnnotationConfigReactiveWebApplicationContext();
|
||||
given(errorAttributes.getErrorAttributes(any(), anyBoolean())).willReturn(getErrorAttributes());
|
||||
given(errorAttributes.getErrorAttributes(any(), anyBoolean(), anyBoolean())).willReturn(getErrorAttributes());
|
||||
DefaultErrorWebExceptionHandler exceptionHandler = new DefaultErrorWebExceptionHandler(errorAttributes,
|
||||
resourceProperties, errorProperties, context);
|
||||
setupViewResolver(exceptionHandler);
|
||||
|
|
|
@ -70,6 +70,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @author Stephane Nicoll
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class BasicErrorControllerIntegrationTests {
|
||||
|
||||
|
@ -84,59 +85,87 @@ class BasicErrorControllerIntegrationTests {
|
|||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
void testErrorForMachineClient() {
|
||||
void testErrorForMachineClientDefault() {
|
||||
load();
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("?trace=true"), Map.class);
|
||||
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", null, "Expected!", "/");
|
||||
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", null,
|
||||
"An error occurred while processing the request", "/");
|
||||
assertThat(entity.getBody().containsKey("exception")).isFalse();
|
||||
assertThat(entity.getBody().containsKey("trace")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testErrorForMachineClientTraceParamTrue() {
|
||||
errorForMachineClientOnTraceParam("?trace=true", true);
|
||||
void testErrorForMachineClientWithParamsTrue() {
|
||||
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");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testErrorForMachineClientTraceParamFalse() {
|
||||
errorForMachineClientOnTraceParam("?trace=false", false);
|
||||
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");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testErrorForMachineClientTraceParamAbsent() {
|
||||
errorForMachineClientOnTraceParam("", false);
|
||||
void testErrorForMachineClientWithParamsAbsent() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-trace-param",
|
||||
"--server.error.include-details=on-details-param");
|
||||
exceptionWithoutStackTraceAndDetails("");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testErrorForMachineClientNeverParams() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-stacktrace=never",
|
||||
"--server.error.include-details=never");
|
||||
exceptionWithoutStackTraceAndDetails("?trace=true&details=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");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testErrorForMachineClientAlwaysParamsWithoutMessage() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-details=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 errorForMachineClientOnTraceParam(String path, boolean expectedTrace) {
|
||||
load("--server.error.include-exception=true", "--server.error.include-stacktrace=on-trace-param");
|
||||
private void exceptionWithStackTraceAndDetails(String path) {
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl(path), Map.class);
|
||||
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", IllegalStateException.class,
|
||||
"Expected!", "/");
|
||||
assertThat(entity.getBody().containsKey("trace")).isEqualTo(expectedTrace);
|
||||
assertThat(entity.getBody().containsKey("trace")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
void testErrorForMachineClientNoStacktrace() {
|
||||
load("--server.error.include-stacktrace=never");
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("?trace=true"), Map.class);
|
||||
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", null, "Expected!", "/");
|
||||
private void exceptionWithoutStackTraceAndDetails(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", "/");
|
||||
assertThat(entity.getBody().containsKey("trace")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
void testErrorForMachineClientAlwaysStacktrace() {
|
||||
load("--server.error.include-stacktrace=always");
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("?trace=false"), Map.class);
|
||||
assertErrorAttributes(entity.getBody(), "500", "Internal Server Error", null, "Expected!", "/");
|
||||
assertThat(entity.getBody().containsKey("trace")).isTrue();
|
||||
void testErrorForAnnotatedExceptionWithoutDetails() {
|
||||
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");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
void testErrorForAnnotatedException() {
|
||||
load("--server.error.include-exception=true");
|
||||
void testErrorForAnnotatedExceptionWithDetails() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-details=always");
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotated"), Map.class);
|
||||
assertErrorAttributes(entity.getBody(), "400", "Bad Request", TestConfiguration.Errors.ExpectedException.class,
|
||||
"Expected!", "/annotated");
|
||||
|
@ -144,25 +173,77 @@ class BasicErrorControllerIntegrationTests {
|
|||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
void testErrorForAnnotatedNoReasonException() {
|
||||
void testErrorForAnnotatedNoReasonExceptionWithoutDetails() {
|
||||
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");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
void testErrorForAnnotatedNoReasonExceptionWithDetails() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-details=always");
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotatedNoReason"), Map.class);
|
||||
assertErrorAttributes(entity.getBody(), "406", "Not Acceptable",
|
||||
TestConfiguration.Errors.NoReasonExpectedException.class, "Expected message", "/annotatedNoReason");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
void testBindingExceptionForMachineClient() {
|
||||
load("--server.error.include-exception=true");
|
||||
RequestEntity request = RequestEntity.get(URI.create(createUrl("/bind"))).accept(MediaType.APPLICATION_JSON)
|
||||
.build();
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().exchange(request, Map.class);
|
||||
String resp = entity.getBody().toString();
|
||||
assertThat(resp).contains("Error count: 1");
|
||||
assertThat(resp).contains("errors=[{");
|
||||
assertThat(resp).contains("codes=[");
|
||||
assertThat(resp).contains("org.springframework.validation.BindException");
|
||||
void testErrorForAnnotatedNoMessageExceptionWithDetails() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-details=always");
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/annotatedNoMessage"), Map.class);
|
||||
assertErrorAttributes(entity.getBody(), "406", "Not Acceptable",
|
||||
TestConfiguration.Errors.NoReasonExpectedException.class, "No message available",
|
||||
"/annotatedNoMessage");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingExceptionForMachineClientWithDetailsParamTrue() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-details=on-details-param");
|
||||
bindingExceptionWithDetails("?details=true");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingExceptionForMachineClientWithDetailsParamFalse() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-details=on-details-param");
|
||||
bindingExceptionWithoutDetails("?details=false");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingExceptionForMachineClientWithDetailsParamAbsent() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-details=on-details-param");
|
||||
bindingExceptionWithoutDetails("");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingExceptionForMachineClientAlwaysDetails() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-details=always");
|
||||
bindingExceptionWithDetails("?details=false");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingExceptionForMachineClientNeverDetails() {
|
||||
load("--server.error.include-exception=true", "--server.error.include-details=never");
|
||||
bindingExceptionWithoutDetails("?details=true");
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes" })
|
||||
private void bindingExceptionWithDetails(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");
|
||||
assertThat(entity.getBody().containsKey("errors")).isTrue();
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes" })
|
||||
private void bindingExceptionWithoutDetails(String param) {
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(createUrl("/bind" + param), Map.class);
|
||||
assertErrorAttributes(entity.getBody(), "400", "Bad Request", BindException.class, "Validation failed",
|
||||
"/bind");
|
||||
assertThat(entity.getBody().containsKey("errors")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -172,22 +253,21 @@ 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);
|
||||
String resp = entity.getBody().toString();
|
||||
assertThat(resp).contains("Error count: 1");
|
||||
assertThat(resp).contains("errors=[{");
|
||||
assertThat(resp).contains("codes=[");
|
||||
assertThat(resp).contains(MethodArgumentNotValidException.class.getName());
|
||||
assertErrorAttributes(entity.getBody(), "400", "Bad Request", MethodArgumentNotValidException.class,
|
||||
"Validation failed", "/bodyValidation");
|
||||
assertThat(entity.getBody().containsKey("errors")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
void testNoExceptionByDefaultForMachineClient() {
|
||||
void testBindingExceptionForMachineClientDefault() {
|
||||
load();
|
||||
RequestEntity request = RequestEntity.get(URI.create(createUrl("/bind"))).accept(MediaType.APPLICATION_JSON)
|
||||
.build();
|
||||
RequestEntity request = RequestEntity.get(URI.create(createUrl("/bind?trace=true,details=true")))
|
||||
.accept(MediaType.APPLICATION_JSON).build();
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().exchange(request, Map.class);
|
||||
String resp = entity.getBody().toString();
|
||||
assertThat(resp).doesNotContain("org.springframework.validation.BindException");
|
||||
assertThat(entity.getBody().containsKey("exception")).isFalse();
|
||||
assertThat(entity.getBody().containsKey("trace")).isFalse();
|
||||
assertThat(entity.getBody().containsKey("errors")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -213,7 +293,7 @@ class BasicErrorControllerIntegrationTests {
|
|||
|
||||
private void assertErrorAttributes(Map<?, ?> content, String status, String error, Class<?> exception,
|
||||
String message, String path) {
|
||||
assertThat(content.get("status")).as("Wrong status").isEqualTo(status);
|
||||
assertThat(content.get("status").toString()).as("Wrong status").isEqualTo(status);
|
||||
assertThat(content.get("error")).as("Wrong error").isEqualTo(error);
|
||||
if (exception != null) {
|
||||
assertThat(content.get("exception")).as("Wrong exception").isEqualTo(exception.getName());
|
||||
|
@ -282,6 +362,11 @@ class BasicErrorControllerIntegrationTests {
|
|||
throw new IllegalStateException("Expected!");
|
||||
}
|
||||
|
||||
@RequestMapping("/noMessage")
|
||||
String noMessage() {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@RequestMapping("/annotated")
|
||||
String annotated() {
|
||||
throw new ExpectedException();
|
||||
|
@ -292,6 +377,11 @@ class BasicErrorControllerIntegrationTests {
|
|||
throw new NoReasonExpectedException("Expected message");
|
||||
}
|
||||
|
||||
@RequestMapping("/annotatedNoMessage")
|
||||
String annotatedNoMessage() {
|
||||
throw new NoReasonExpectedException("");
|
||||
}
|
||||
|
||||
@RequestMapping("/bind")
|
||||
String bind() throws Exception {
|
||||
BindException error = new BindException(this, "test");
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -67,8 +67,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||
* {@link SpringBootTest @SpringBootTest}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
@SpringBootTest
|
||||
@SpringBootTest(properties = { "server.error.include-details=always" })
|
||||
@DirtiesContext
|
||||
class BasicErrorControllerMockMvcTests {
|
||||
|
||||
|
@ -117,7 +118,7 @@ class BasicErrorControllerMockMvcTests {
|
|||
// And the rendered status code is always wrong (but would be 400 in a real
|
||||
// system)
|
||||
String content = response.getResponse().getContentAsString();
|
||||
assertThat(content).contains("Error count: 1");
|
||||
assertThat(content).contains("Validation failed");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -50,8 +50,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||
* Integration tests for the default error view.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
@SpringBootTest
|
||||
@SpringBootTest(properties = { "server.error.include-details=always" })
|
||||
@DirtiesContext
|
||||
class DefaultErrorViewIntegrationTests {
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -45,13 +45,13 @@ class ErrorMvcAutoConfigurationTests {
|
|||
AutoConfigurations.of(DispatcherServletAutoConfiguration.class, ErrorMvcAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void renderContainsViewWithExceptionDetails() throws Exception {
|
||||
void renderContainsViewWithExceptionDetails() {
|
||||
this.contextRunner.run((context) -> {
|
||||
View errorView = context.getBean("error", View.class);
|
||||
ErrorAttributes errorAttributes = context.getBean(ErrorAttributes.class);
|
||||
DispatcherServletWebRequest webRequest = createWebRequest(new IllegalStateException("Exception message"),
|
||||
false);
|
||||
errorView.render(errorAttributes.getErrorAttributes(webRequest, true), webRequest.getRequest(),
|
||||
errorView.render(errorAttributes.getErrorAttributes(webRequest, 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), webRequest.getRequest(),
|
||||
errorView.render(errorAttributes.getErrorAttributes(webRequest, true, true), webRequest.getRequest(),
|
||||
webRequest.getResponse());
|
||||
assertThat(output).contains("Cannot render error page for request [/path] "
|
||||
+ "and exception [Exception message] as the response has "
|
||||
|
|
|
@ -71,6 +71,7 @@ public class DevToolsPropertyDefaultsPostProcessor implements EnvironmentPostPro
|
|||
properties.put("spring.resources.chain.cache", "false");
|
||||
properties.put("spring.template.provider.cache", "false");
|
||||
properties.put("spring.mvc.log-resolved-exception", "true");
|
||||
properties.put("server.error.include-details", "ALWAYS");
|
||||
properties.put("server.error.include-stacktrace", "ALWAYS");
|
||||
properties.put("server.servlet.jsp.init-parameters.development", "true");
|
||||
properties.put("spring.reactor.debug", "true");
|
||||
|
|
|
@ -106,8 +106,10 @@ class DevToolPropertiesIntegrationTests {
|
|||
application.setWebApplicationType(WebApplicationType.NONE);
|
||||
this.context = getContext(application::run);
|
||||
ConfigurableEnvironment environment = this.context.getEnvironment();
|
||||
String property = environment.getProperty("server.error.include-stacktrace");
|
||||
assertThat(property).isEqualTo(ErrorProperties.IncludeStacktrace.ALWAYS.toString());
|
||||
String includeStackTrace = environment.getProperty("server.error.include-stacktrace");
|
||||
assertThat(includeStackTrace).isEqualTo(ErrorProperties.IncludeStacktrace.ALWAYS.toString());
|
||||
String includeDetails = environment.getProperty("server.error.include-details");
|
||||
assertThat(includeDetails).isEqualTo(ErrorProperties.IncludeDetails.ALWAYS.toString());
|
||||
}
|
||||
|
||||
protected ConfigurableApplicationContext getContext(Supplier<ConfigurableApplicationContext> supplier)
|
||||
|
|
|
@ -53,6 +53,7 @@ import org.springframework.web.server.ServerWebExchange;
|
|||
* @author Brian Clozel
|
||||
* @author Stephane Nicoll
|
||||
* @author Michele Mancioppi
|
||||
* @author Scott Frederick
|
||||
* @since 2.0.0
|
||||
* @see ErrorAttributes
|
||||
*/
|
||||
|
@ -79,7 +80,14 @@ public class DefaultErrorAttributes implements ErrorAttributes {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
|
||||
return this.getErrorAttributes(request, includeStackTrace, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace,
|
||||
boolean includeDetails) {
|
||||
Map<String, Object> errorAttributes = new LinkedHashMap<>();
|
||||
errorAttributes.put("timestamp", new Date());
|
||||
errorAttributes.put("path", request.path());
|
||||
|
@ -89,9 +97,9 @@ public class DefaultErrorAttributes implements ErrorAttributes {
|
|||
HttpStatus errorStatus = determineHttpStatus(error, responseStatusAnnotation);
|
||||
errorAttributes.put("status", errorStatus.value());
|
||||
errorAttributes.put("error", errorStatus.getReasonPhrase());
|
||||
errorAttributes.put("message", determineMessage(error, responseStatusAnnotation));
|
||||
errorAttributes.put("message", determineMessage(error, responseStatusAnnotation, includeDetails));
|
||||
errorAttributes.put("requestId", request.exchange().getRequest().getId());
|
||||
handleException(errorAttributes, determineException(error), includeStackTrace);
|
||||
handleException(errorAttributes, determineException(error), includeStackTrace, includeDetails);
|
||||
return errorAttributes;
|
||||
}
|
||||
|
||||
|
@ -102,9 +110,13 @@ public class DefaultErrorAttributes implements ErrorAttributes {
|
|||
return responseStatusAnnotation.getValue("code", HttpStatus.class).orElse(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
private String determineMessage(Throwable error, MergedAnnotation<ResponseStatus> responseStatusAnnotation) {
|
||||
private String determineMessage(Throwable error, MergedAnnotation<ResponseStatus> responseStatusAnnotation,
|
||||
boolean includeDetails) {
|
||||
if (error instanceof WebExchangeBindException) {
|
||||
return error.getMessage();
|
||||
return includeDetails ? error.getMessage() : "Validation failed";
|
||||
}
|
||||
if (!includeDetails) {
|
||||
return "";
|
||||
}
|
||||
if (error instanceof ResponseStatusException) {
|
||||
return ((ResponseStatusException) error).getReason();
|
||||
|
@ -130,14 +142,15 @@ public class DefaultErrorAttributes implements ErrorAttributes {
|
|||
errorAttributes.put("trace", stackTrace.toString());
|
||||
}
|
||||
|
||||
private void handleException(Map<String, Object> errorAttributes, Throwable error, boolean includeStackTrace) {
|
||||
private void handleException(Map<String, Object> errorAttributes, Throwable error, boolean includeStackTrace,
|
||||
boolean includeDetails) {
|
||||
if (this.includeException) {
|
||||
errorAttributes.put("exception", error.getClass().getName());
|
||||
}
|
||||
if (includeStackTrace) {
|
||||
addStackTrace(errorAttributes, error);
|
||||
}
|
||||
if (error instanceof BindingResult) {
|
||||
if (includeDetails && (error instanceof BindingResult)) {
|
||||
BindingResult result = (BindingResult) error;
|
||||
if (result.hasErrors()) {
|
||||
errorAttributes.put("errors", result.getAllErrors());
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -26,6 +26,7 @@ import org.springframework.web.server.ServerWebExchange;
|
|||
* Provides access to error attributes which can be logged or presented to the user.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Scott Frederick
|
||||
* @since 2.0.0
|
||||
* @see DefaultErrorAttributes
|
||||
*/
|
||||
|
@ -37,9 +38,21 @@ public interface ErrorAttributes {
|
|||
* @param request the source request
|
||||
* @param includeStackTrace if stack trace elements should be included
|
||||
* @return a map of error attributes
|
||||
* @deprecated since 2.3.0 in favor of
|
||||
* {@link #getErrorAttributes(ServerRequest, boolean, boolean)}
|
||||
*/
|
||||
Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace);
|
||||
|
||||
/**
|
||||
* Return a {@link Map} of the error attributes. The map can be used as the model of
|
||||
* an error page, or returned as a {@link ServerResponse} body.
|
||||
* @param request the source request
|
||||
* @param includeStackTrace if stack trace elements should be included
|
||||
* @param includeDetails if message and errors elements should be included
|
||||
* @return a map of error attributes
|
||||
*/
|
||||
Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace, boolean includeDetails);
|
||||
|
||||
/**
|
||||
* Return the underlying cause of the error or {@code null} if the error cannot be
|
||||
* extracted.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -46,9 +46,10 @@ import org.springframework.web.servlet.ModelAndView;
|
|||
* <li>status - The status code</li>
|
||||
* <li>error - The error reason</li>
|
||||
* <li>exception - The class name of the root exception (if configured)</li>
|
||||
* <li>message - The exception message</li>
|
||||
* <li>errors - Any {@link ObjectError}s from a {@link BindingResult} exception
|
||||
* <li>trace - The exception stack trace</li>
|
||||
* <li>message - The exception message (if configured)</li>
|
||||
* <li>errors - Any {@link ObjectError}s from a {@link BindingResult} exception (if
|
||||
* configured)</li>
|
||||
* <li>trace - The exception stack trace (if configured)</li>
|
||||
* <li>path - The URL path when the exception was raised</li>
|
||||
* </ul>
|
||||
*
|
||||
|
@ -56,6 +57,7 @@ import org.springframework.web.servlet.ModelAndView;
|
|||
* @author Dave Syer
|
||||
* @author Stephane Nicoll
|
||||
* @author Vedran Pavic
|
||||
* @author Scott Frederick
|
||||
* @since 2.0.0
|
||||
* @see ErrorAttributes
|
||||
*/
|
||||
|
@ -99,11 +101,18 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException
|
|||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
|
||||
return this.getErrorAttributes(webRequest, includeStackTrace, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace,
|
||||
boolean includeDetails) {
|
||||
Map<String, Object> errorAttributes = new LinkedHashMap<>();
|
||||
errorAttributes.put("timestamp", new Date());
|
||||
addStatus(errorAttributes, webRequest);
|
||||
addErrorDetails(errorAttributes, webRequest, includeStackTrace);
|
||||
addErrorDetails(errorAttributes, webRequest, includeStackTrace, includeDetails);
|
||||
addPath(errorAttributes, webRequest);
|
||||
return errorAttributes;
|
||||
}
|
||||
|
@ -125,8 +134,8 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException
|
|||
}
|
||||
}
|
||||
|
||||
private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest,
|
||||
boolean includeStackTrace) {
|
||||
private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest, boolean includeStackTrace,
|
||||
boolean includeDetails) {
|
||||
Throwable error = getError(webRequest);
|
||||
if (error != null) {
|
||||
while (error instanceof ServletException && error.getCause() != null) {
|
||||
|
@ -135,32 +144,51 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException
|
|||
if (this.includeException) {
|
||||
errorAttributes.put("exception", error.getClass().getName());
|
||||
}
|
||||
addErrorMessage(errorAttributes, error);
|
||||
if (includeStackTrace) {
|
||||
addStackTrace(errorAttributes, error);
|
||||
}
|
||||
}
|
||||
Object message = getAttribute(webRequest, "javax.servlet.error.message");
|
||||
if ((!StringUtils.isEmpty(message) || errorAttributes.get("message") == null)
|
||||
&& !(error instanceof BindingResult)) {
|
||||
errorAttributes.put("message", StringUtils.isEmpty(message) ? "No message available" : message);
|
||||
addErrorMessage(errorAttributes, webRequest, error, includeDetails);
|
||||
}
|
||||
|
||||
private void addErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error,
|
||||
boolean includeDetails) {
|
||||
BindingResult result = extractBindingResult(error);
|
||||
if (result == null) {
|
||||
addExceptionErrorMessage(errorAttributes, webRequest, error, includeDetails);
|
||||
}
|
||||
else {
|
||||
addBindingResultErrorMessage(errorAttributes, result, includeDetails);
|
||||
}
|
||||
}
|
||||
|
||||
private void addErrorMessage(Map<String, Object> errorAttributes, Throwable error) {
|
||||
BindingResult result = extractBindingResult(error);
|
||||
if (result == null) {
|
||||
errorAttributes.put("message", error.getMessage());
|
||||
private void addExceptionErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error,
|
||||
boolean includeDetails) {
|
||||
if (!includeDetails) {
|
||||
errorAttributes.put("message", "An error occurred while processing the request");
|
||||
return;
|
||||
}
|
||||
Object message = getAttribute(webRequest, "javax.servlet.error.message");
|
||||
if (StringUtils.isEmpty(message) && error != null) {
|
||||
message = error.getMessage();
|
||||
}
|
||||
if (StringUtils.isEmpty(message)) {
|
||||
message = "No message available";
|
||||
}
|
||||
errorAttributes.put("message", message);
|
||||
}
|
||||
|
||||
private void addBindingResultErrorMessage(Map<String, Object> errorAttributes, BindingResult result,
|
||||
boolean includeDetails) {
|
||||
if (!includeDetails) {
|
||||
errorAttributes.put("message", "Validation failed");
|
||||
return;
|
||||
}
|
||||
if (result.hasErrors()) {
|
||||
errorAttributes.put("errors", result.getAllErrors());
|
||||
errorAttributes.put("message", "Validation failed for object='" + result.getObjectName()
|
||||
+ "'. Error count: " + result.getErrorCount());
|
||||
}
|
||||
else {
|
||||
errorAttributes.put("message", "No errors");
|
||||
}
|
||||
errorAttributes.put("message", "Validation failed for object='" + result.getObjectName() + "'. "
|
||||
+ "Error count: " + result.getErrorCount());
|
||||
}
|
||||
|
||||
private BindingResult extractBindingResult(Throwable error) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -26,6 +26,7 @@ import org.springframework.web.servlet.ModelAndView;
|
|||
* Provides access to error attributes which can be logged or presented to the user.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
* @since 2.0.0
|
||||
* @see DefaultErrorAttributes
|
||||
*/
|
||||
|
@ -38,9 +39,23 @@ public interface ErrorAttributes {
|
|||
* @param webRequest the source request
|
||||
* @param includeStackTrace if stack trace elements should be included
|
||||
* @return a map of error attributes
|
||||
* @deprecated since 2.3.0 in favor of
|
||||
* {@link #getErrorAttributes(WebRequest, boolean, boolean)}
|
||||
*/
|
||||
@Deprecated
|
||||
Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace);
|
||||
|
||||
/**
|
||||
* Returns a {@link Map} of the error attributes. The map can be used as the model of
|
||||
* an error page {@link ModelAndView}, or returned as a
|
||||
* {@link ResponseBody @ResponseBody}.
|
||||
* @param webRequest the source request
|
||||
* @param includeStackTrace if stack trace elements should be included
|
||||
* @param includeDetails if message and errors elements should be included
|
||||
* @return a map of error attributes
|
||||
*/
|
||||
Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace, boolean includeDetails);
|
||||
|
||||
/**
|
||||
* Return the underlying cause of the error or {@code null} if the error cannot be
|
||||
* extracted.
|
||||
|
|
|
@ -47,6 +47,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
|||
*
|
||||
* @author Brian Clozel
|
||||
* @author Stephane Nicoll
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class DefaultErrorAttributesTests {
|
||||
|
||||
|
@ -60,7 +61,8 @@ class DefaultErrorAttributesTests {
|
|||
void missingExceptionAttribute() {
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test").build());
|
||||
ServerRequest request = ServerRequest.create(exchange, this.readers);
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.errorAttributes.getErrorAttributes(request, false))
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.errorAttributes.getErrorAttributes(request, false, false))
|
||||
.withMessageContaining("Missing exception attribute in ServerWebExchange");
|
||||
}
|
||||
|
||||
|
@ -68,7 +70,7 @@ class DefaultErrorAttributesTests {
|
|||
void includeTimeStamp() {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, NOT_FOUND),
|
||||
false);
|
||||
false, false);
|
||||
assertThat(attributes.get("timestamp")).isInstanceOf(Date.class);
|
||||
}
|
||||
|
||||
|
@ -77,7 +79,7 @@ class DefaultErrorAttributesTests {
|
|||
Error error = new OutOfMemoryError("Test error");
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, error),
|
||||
false);
|
||||
false, false);
|
||||
assertThat(attributes.get("error")).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
|
||||
assertThat(attributes.get("status")).isEqualTo(500);
|
||||
}
|
||||
|
@ -87,7 +89,7 @@ class DefaultErrorAttributesTests {
|
|||
Exception error = new CustomException();
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, error),
|
||||
false);
|
||||
false, false);
|
||||
assertThat(attributes.get("error")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.getReasonPhrase());
|
||||
assertThat(attributes.get("message")).isEqualTo("");
|
||||
assertThat(attributes.get("status")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.value());
|
||||
|
@ -98,7 +100,7 @@ class DefaultErrorAttributesTests {
|
|||
Exception error = new CustomException("Test Message");
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, error),
|
||||
false);
|
||||
false, true);
|
||||
assertThat(attributes.get("error")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.getReasonPhrase());
|
||||
assertThat(attributes.get("message")).isEqualTo("Test Message");
|
||||
assertThat(attributes.get("status")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.value());
|
||||
|
@ -109,7 +111,7 @@ class DefaultErrorAttributesTests {
|
|||
Exception error = new Custom2Exception();
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, error),
|
||||
false);
|
||||
false, true);
|
||||
assertThat(attributes.get("error")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.getReasonPhrase());
|
||||
assertThat(attributes.get("status")).isEqualTo(HttpStatus.I_AM_A_TEAPOT.value());
|
||||
assertThat(attributes.get("message")).isEqualTo("Nope!");
|
||||
|
@ -119,7 +121,7 @@ class DefaultErrorAttributesTests {
|
|||
void includeStatusCode() {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, NOT_FOUND),
|
||||
false);
|
||||
false, false);
|
||||
assertThat(attributes.get("error")).isEqualTo(HttpStatus.NOT_FOUND.getReasonPhrase());
|
||||
assertThat(attributes.get("status")).isEqualTo(404);
|
||||
}
|
||||
|
@ -129,19 +131,29 @@ class DefaultErrorAttributesTests {
|
|||
Error error = new OutOfMemoryError("Test error");
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
|
||||
ServerRequest serverRequest = buildServerRequest(request, error);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true);
|
||||
assertThat(this.errorAttributes.getError(serverRequest)).isSameAs(error);
|
||||
assertThat(attributes.get("exception")).isNull();
|
||||
assertThat(attributes.get("message")).isEqualTo("Test error");
|
||||
}
|
||||
|
||||
@Test
|
||||
void excludeDetails() {
|
||||
Error error = new OutOfMemoryError("Test error");
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
|
||||
ServerRequest serverRequest = buildServerRequest(request, error);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, false);
|
||||
assertThat(this.errorAttributes.getError(serverRequest)).isSameAs(error);
|
||||
assertThat(attributes.get("message")).isEqualTo("");
|
||||
}
|
||||
|
||||
@Test
|
||||
void includeException() {
|
||||
RuntimeException error = new RuntimeException("Test");
|
||||
this.errorAttributes = new DefaultErrorAttributes(true);
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
|
||||
ServerRequest serverRequest = buildServerRequest(request, error);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true);
|
||||
assertThat(this.errorAttributes.getError(serverRequest)).isSameAs(error);
|
||||
assertThat(attributes.get("exception")).isEqualTo(RuntimeException.class.getName());
|
||||
assertThat(attributes.get("message")).isEqualTo("Test");
|
||||
|
@ -154,7 +166,7 @@ class DefaultErrorAttributesTests {
|
|||
this.errorAttributes = new DefaultErrorAttributes(true);
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
|
||||
ServerRequest serverRequest = buildServerRequest(request, error);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true);
|
||||
assertThat(attributes.get("status")).isEqualTo(400);
|
||||
assertThat(attributes.get("message")).isEqualTo("invalid request");
|
||||
assertThat(attributes.get("exception")).isEqualTo(RuntimeException.class.getName());
|
||||
|
@ -168,7 +180,7 @@ class DefaultErrorAttributesTests {
|
|||
this.errorAttributes = new DefaultErrorAttributes(true);
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
|
||||
ServerRequest serverRequest = buildServerRequest(request, error);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, true);
|
||||
assertThat(attributes.get("status")).isEqualTo(406);
|
||||
assertThat(attributes.get("message")).isEqualTo("could not process request");
|
||||
assertThat(attributes.get("exception")).isEqualTo(ResponseStatusException.class.getName());
|
||||
|
@ -179,7 +191,7 @@ class DefaultErrorAttributesTests {
|
|||
void notIncludeTrace() {
|
||||
RuntimeException ex = new RuntimeException("Test");
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex),
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), false,
|
||||
false);
|
||||
assertThat(attributes.get("trace")).isNull();
|
||||
}
|
||||
|
@ -188,7 +200,8 @@ class DefaultErrorAttributesTests {
|
|||
void includeTrace() {
|
||||
RuntimeException ex = new RuntimeException("Test");
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), true);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), true,
|
||||
false);
|
||||
assertThat(attributes.get("trace").toString()).startsWith("java.lang");
|
||||
}
|
||||
|
||||
|
@ -196,7 +209,7 @@ class DefaultErrorAttributesTests {
|
|||
void includePath() {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, NOT_FOUND),
|
||||
false);
|
||||
false, false);
|
||||
assertThat(attributes.get("path")).isEqualTo("/test");
|
||||
}
|
||||
|
||||
|
@ -204,7 +217,7 @@ class DefaultErrorAttributesTests {
|
|||
void includeLogPrefix() {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
|
||||
ServerRequest serverRequest = buildServerRequest(request, NOT_FOUND);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(serverRequest, false, false);
|
||||
assertThat(attributes.get("requestId")).isEqualTo(serverRequest.exchange().getRequest().getId());
|
||||
}
|
||||
|
||||
|
@ -216,8 +229,8 @@ class DefaultErrorAttributesTests {
|
|||
bindingResult.addError(new ObjectError("c", "d"));
|
||||
Exception ex = new WebExchangeBindException(stringParam, bindingResult);
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex),
|
||||
false);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), false,
|
||||
true);
|
||||
assertThat(attributes.get("message")).asString()
|
||||
.startsWith("Validation failed for argument at index 0 in method: "
|
||||
+ "int org.springframework.boot.web.reactive.error.DefaultErrorAttributesTests"
|
||||
|
@ -225,6 +238,20 @@ class DefaultErrorAttributesTests {
|
|||
assertThat(attributes.get("errors")).isEqualTo(bindingResult.getAllErrors());
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractBindingResultErrorsExcludeDetails() throws Exception {
|
||||
Method method = getClass().getDeclaredMethod("method", String.class);
|
||||
MethodParameter stringParam = new MethodParameter(method, 0);
|
||||
BindingResult bindingResult = new MapBindingResult(Collections.singletonMap("a", "b"), "objectName");
|
||||
bindingResult.addError(new ObjectError("c", "d"));
|
||||
Exception ex = new WebExchangeBindException(stringParam, bindingResult);
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, ex), false,
|
||||
false);
|
||||
assertThat(attributes.get("message")).isEqualTo("Validation failed");
|
||||
assertThat(attributes.containsKey("errors")).isFalse();
|
||||
}
|
||||
|
||||
private ServerRequest buildServerRequest(MockServerHttpRequest request, Throwable error) {
|
||||
ServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||
this.errorAttributes.storeErrorInformation(error, exchange);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -42,32 +42,33 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
*
|
||||
* @author Phillip Webb
|
||||
* @author Vedran Pavic
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class DefaultErrorAttributesTests {
|
||||
|
||||
private DefaultErrorAttributes errorAttributes = new DefaultErrorAttributes();
|
||||
private final DefaultErrorAttributes errorAttributes = new DefaultErrorAttributes();
|
||||
|
||||
private MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
private final MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
|
||||
private WebRequest webRequest = new ServletWebRequest(this.request);
|
||||
private final WebRequest webRequest = new ServletWebRequest(this.request);
|
||||
|
||||
@Test
|
||||
void includeTimeStamp() {
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false);
|
||||
assertThat(attributes.get("timestamp")).isInstanceOf(Date.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void specificStatusCode() {
|
||||
this.request.setAttribute("javax.servlet.error.status_code", 404);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false);
|
||||
assertThat(attributes.get("error")).isEqualTo(HttpStatus.NOT_FOUND.getReasonPhrase());
|
||||
assertThat(attributes.get("status")).isEqualTo(404);
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingStatusCode() {
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false);
|
||||
assertThat(attributes.get("error")).isEqualTo("None");
|
||||
assertThat(attributes.get("status")).isEqualTo(999);
|
||||
}
|
||||
|
@ -77,48 +78,74 @@ class DefaultErrorAttributesTests {
|
|||
RuntimeException ex = new RuntimeException("Test");
|
||||
ModelAndView modelAndView = this.errorAttributes.resolveException(this.request, null, null, ex);
|
||||
this.request.setAttribute("javax.servlet.error.exception", new RuntimeException("Ignored"));
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true);
|
||||
assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(ex);
|
||||
assertThat(modelAndView).isNull();
|
||||
assertThat(attributes.get("exception")).isNull();
|
||||
assertThat(attributes.containsKey("exception")).isFalse();
|
||||
assertThat(attributes.get("message")).isEqualTo("Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void servletError() {
|
||||
void servletErrorWithDetail() {
|
||||
RuntimeException ex = new RuntimeException("Test");
|
||||
this.request.setAttribute("javax.servlet.error.exception", ex);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true);
|
||||
assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(ex);
|
||||
assertThat(attributes.get("exception")).isNull();
|
||||
assertThat(attributes.containsKey("exception")).isFalse();
|
||||
assertThat(attributes.get("message")).isEqualTo("Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void servletMessage() {
|
||||
void servletErrorWithoutDetail() {
|
||||
RuntimeException ex = new RuntimeException("Test");
|
||||
this.request.setAttribute("javax.servlet.error.exception", ex);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false);
|
||||
assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(ex);
|
||||
assertThat(attributes.containsKey("exception")).isFalse();
|
||||
assertThat(attributes.get("message").toString()).contains("An error occurred");
|
||||
}
|
||||
|
||||
@Test
|
||||
void servletMessageWithDetail() {
|
||||
this.request.setAttribute("javax.servlet.error.message", "Test");
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
|
||||
assertThat(attributes.get("exception")).isNull();
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true);
|
||||
assertThat(attributes.containsKey("exception")).isFalse();
|
||||
assertThat(attributes.get("message")).isEqualTo("Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullMessage() {
|
||||
void servletMessageWithoutDetail() {
|
||||
this.request.setAttribute("javax.servlet.error.message", "Test");
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false);
|
||||
assertThat(attributes.containsKey("exception")).isFalse();
|
||||
assertThat(attributes.get("message")).asString().contains("An error occurred");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullExceptionMessage() {
|
||||
this.request.setAttribute("javax.servlet.error.exception", new RuntimeException());
|
||||
this.request.setAttribute("javax.servlet.error.message", "Test");
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
|
||||
assertThat(attributes.get("exception")).isNull();
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true);
|
||||
assertThat(attributes.containsKey("exception")).isFalse();
|
||||
assertThat(attributes.get("message")).isEqualTo("Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullExceptionMessageAndServletMessage() {
|
||||
this.request.setAttribute("javax.servlet.error.exception", new RuntimeException());
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true);
|
||||
assertThat(attributes.containsKey("exception")).isFalse();
|
||||
assertThat(attributes.get("message")).isEqualTo("No message available");
|
||||
}
|
||||
|
||||
@Test
|
||||
void unwrapServletException() {
|
||||
RuntimeException ex = new RuntimeException("Test");
|
||||
ServletException wrapped = new ServletException(new ServletException(ex));
|
||||
this.request.setAttribute("javax.servlet.error.exception", wrapped);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true);
|
||||
assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(wrapped);
|
||||
assertThat(attributes.get("exception")).isNull();
|
||||
assertThat(attributes.containsKey("exception")).isFalse();
|
||||
assertThat(attributes.get("message")).isEqualTo("Test");
|
||||
}
|
||||
|
||||
|
@ -126,65 +153,81 @@ class DefaultErrorAttributesTests {
|
|||
void getError() {
|
||||
Error error = new OutOfMemoryError("Test error");
|
||||
this.request.setAttribute("javax.servlet.error.exception", error);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, true);
|
||||
assertThat(this.errorAttributes.getError(this.webRequest)).isSameAs(error);
|
||||
assertThat(attributes.get("exception")).isNull();
|
||||
assertThat(attributes.containsKey("exception")).isFalse();
|
||||
assertThat(attributes.get("message")).isEqualTo("Test error");
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractBindingResultErrors() {
|
||||
void withBindingErrors() {
|
||||
BindingResult bindingResult = new MapBindingResult(Collections.singletonMap("a", "b"), "objectName");
|
||||
bindingResult.addError(new ObjectError("c", "d"));
|
||||
Exception ex = new BindException(bindingResult);
|
||||
testBindingResult(bindingResult, ex);
|
||||
testBindingResult(bindingResult, ex, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractMethodArgumentNotValidExceptionBindingResultErrors() {
|
||||
void withoutBindingErrors() {
|
||||
BindingResult bindingResult = new MapBindingResult(Collections.singletonMap("a", "b"), "objectName");
|
||||
bindingResult.addError(new ObjectError("c", "d"));
|
||||
Exception ex = new BindException(bindingResult);
|
||||
testBindingResult(bindingResult, ex, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withMethodArgumentNotValidExceptionBindingErrors() {
|
||||
BindingResult bindingResult = new MapBindingResult(Collections.singletonMap("a", "b"), "objectName");
|
||||
bindingResult.addError(new ObjectError("c", "d"));
|
||||
Exception ex = new MethodArgumentNotValidException(null, bindingResult);
|
||||
testBindingResult(bindingResult, ex);
|
||||
testBindingResult(bindingResult, ex, true);
|
||||
}
|
||||
|
||||
private void testBindingResult(BindingResult bindingResult, Exception ex) {
|
||||
private void testBindingResult(BindingResult bindingResult, Exception ex, boolean includeDetails) {
|
||||
this.request.setAttribute("javax.servlet.error.exception", ex);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
|
||||
assertThat(attributes.get("message")).isEqualTo("Validation failed for object='objectName'. Error count: 1");
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false,
|
||||
includeDetails);
|
||||
if (includeDetails) {
|
||||
assertThat(attributes.get("message"))
|
||||
.isEqualTo("Validation failed for object='objectName'. Error count: 1");
|
||||
assertThat(attributes.get("errors")).isEqualTo(bindingResult.getAllErrors());
|
||||
}
|
||||
else {
|
||||
assertThat(attributes.get("message")).isEqualTo("Validation failed");
|
||||
assertThat(attributes.containsKey("errors")).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void withExceptionAttribute() {
|
||||
DefaultErrorAttributes errorAttributes = new DefaultErrorAttributes(true);
|
||||
RuntimeException ex = new RuntimeException("Test");
|
||||
this.request.setAttribute("javax.servlet.error.exception", ex);
|
||||
Map<String, Object> attributes = errorAttributes.getErrorAttributes(this.webRequest, false);
|
||||
Map<String, Object> attributes = errorAttributes.getErrorAttributes(this.webRequest, false, true);
|
||||
assertThat(attributes.get("exception")).isEqualTo(RuntimeException.class.getName());
|
||||
assertThat(attributes.get("message")).isEqualTo("Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void trace() {
|
||||
void withStackTraceAttribute() {
|
||||
RuntimeException ex = new RuntimeException("Test");
|
||||
this.request.setAttribute("javax.servlet.error.exception", ex);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, true);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, true, false);
|
||||
assertThat(attributes.get("trace").toString()).startsWith("java.lang");
|
||||
}
|
||||
|
||||
@Test
|
||||
void noTrace() {
|
||||
void withoutStackTraceAttribute() {
|
||||
RuntimeException ex = new RuntimeException("Test");
|
||||
this.request.setAttribute("javax.servlet.error.exception", ex);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
|
||||
assertThat(attributes.get("trace")).isNull();
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false);
|
||||
assertThat(attributes.containsKey("trace")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void path() {
|
||||
this.request.setAttribute("javax.servlet.error.request_uri", "path");
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false);
|
||||
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, false, false);
|
||||
assertThat(attributes.get("path")).isEqualTo("path");
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -34,8 +34,10 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Stephane Nicoll
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
properties = { "server.error.include-details=always" })
|
||||
class SampleActuatorCustomSecurityApplicationTests extends AbstractSampleActuatorCustomSecurityTests {
|
||||
|
||||
@LocalServerPort
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -39,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { "server.error.include-details=always" })
|
||||
class SampleActuatorUiApplicationTests {
|
||||
|
||||
@Autowired
|
||||
|
|
Loading…
Reference in New Issue