From 05b73ceeecd940843a34b9026ce73ba408a7aaa2 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 21 Aug 2024 17:32:27 -0700 Subject: [PATCH] Call getErrorAttributes() only once Refine the fix introduced in commit 60b7e6cf23 so that the `getErrorAttributes()` method is not called multiple times. If the status is missing, the `DefaultErrorWebExceptionHandler` will now call an internal `DefaultErrorAttributes` instance in order to obtain the actual status result. Fixes gh-41732 --- .../DefaultErrorWebExceptionHandler.java | 12 +++++-- ...orWebExceptionHandlerIntegrationTests.java | 36 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandler.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandler.java index 5ce76162ef58..01b615615ef0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandler.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandler.java @@ -30,6 +30,7 @@ import org.springframework.boot.autoconfigure.web.WebProperties.Resources; import org.springframework.boot.web.error.ErrorAttributeOptions; import org.springframework.boot.web.error.ErrorAttributeOptions.Include; +import org.springframework.boot.web.reactive.error.DefaultErrorAttributes; import org.springframework.boot.web.reactive.error.ErrorAttributes; import org.springframework.context.ApplicationContext; import org.springframework.http.HttpStatus; @@ -94,6 +95,8 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa private static final ErrorAttributeOptions ONLY_STATUS = ErrorAttributeOptions.of(Include.STATUS); + private static final DefaultErrorAttributes defaultErrorAttributes = new DefaultErrorAttributes(); + private final ErrorProperties errorProperties; /** @@ -121,8 +124,8 @@ protected RouterFunction getRoutingFunction(ErrorAttributes erro * @return a {@code Publisher} of the HTTP response */ protected Mono renderErrorView(ServerRequest request) { - int status = getHttpStatus(getErrorAttributes(request, ONLY_STATUS)); Map errorAttributes = getErrorAttributes(request, MediaType.TEXT_HTML); + int status = getHttpStatus(request, errorAttributes); ServerResponse.BodyBuilder responseBody = ServerResponse.status(status).contentType(TEXT_HTML_UTF8); return Flux.just(getData(status).toArray(new String[] {})) .flatMap((viewName) -> renderErrorView(viewName, responseBody, errorAttributes)) @@ -148,8 +151,8 @@ private List getData(int errorStatus) { * @return a {@code Publisher} of the HTTP response */ protected Mono renderErrorResponse(ServerRequest request) { - int status = getHttpStatus(getErrorAttributes(request, ONLY_STATUS)); Map errorAttributes = getErrorAttributes(request, MediaType.ALL); + int status = getHttpStatus(request, errorAttributes); return ServerResponse.status(status) .contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(errorAttributes)); @@ -234,6 +237,11 @@ protected boolean isIncludePath(ServerRequest request, MediaType produces) { }; } + private int getHttpStatus(ServerRequest request, Map errorAttributes) { + return getHttpStatus(errorAttributes.containsKey("status") ? errorAttributes + : defaultErrorAttributes.getErrorAttributes(request, ONLY_STATUS)); + } + /** * Get the HTTP error status information from the error map. * @param errorAttributes the current error information diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerIntegrationTests.java index 8aec51f70eb8..838055707b7b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerIntegrationTests.java @@ -18,6 +18,7 @@ import java.nio.charset.StandardCharsets; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import jakarta.validation.Valid; @@ -597,6 +598,21 @@ void customErrorWebExceptionHandlerWithoutStatus() { }); } + @Test + void customErrorAttributesWithoutStatus() { + this.contextRunner.withUserConfiguration(CustomErrorAttributesWithoutStatus.class).run((context) -> { + WebTestClient client = getWebClient(context); + client.get() + .uri("/badRequest") + .exchange() + .expectStatus() + .isBadRequest() + .expectBody() + .jsonPath("status") + .doesNotExist(); + }); + } + private String getErrorTemplatesLocation() { String packageName = getClass().getPackage().getName(); return "classpath:/" + packageName.replace('.', '/') + "/templates/"; @@ -686,6 +702,7 @@ static class CustomErrorAttributesWithoutDelegation { @Bean ErrorAttributes errorAttributes() { return new DefaultErrorAttributes() { + @Override public Map getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) { Map errorAttributes = new HashMap<>(); @@ -724,4 +741,23 @@ protected ErrorAttributeOptions getErrorAttributeOptions(ServerRequest request, } + @Configuration(proxyBeanMethods = false) + static class CustomErrorAttributesWithoutStatus { + + @Bean + ErrorAttributes errorAttributes() { + return new DefaultErrorAttributes() { + + @Override + public Map getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) { + Map attributes = new LinkedHashMap<>(super.getErrorAttributes(request, options)); + attributes.remove("status"); + return attributes; + } + + }; + } + + } + }