diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java index 0399bbcdc2c2..030efea08e10 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java @@ -34,6 +34,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.core.NestedExceptionUtils; import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpLogging; import org.springframework.http.HttpStatus; import org.springframework.http.codec.HttpMessageReader; @@ -71,6 +72,20 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept DISCONNECTED_CLIENT_EXCEPTIONS = Collections.unmodifiableSet(exceptions); } + private static final Set RESPONSE_CONTENT_HEADERS; + + static { + Set headers = new HashSet<>(); + headers.add(HttpHeaders.CONTENT_ENCODING); + headers.add(HttpHeaders.CONTENT_DISPOSITION); + headers.add(HttpHeaders.CONTENT_LANGUAGE); + headers.add(HttpHeaders.CONTENT_LENGTH); + headers.add(HttpHeaders.CONTENT_LOCATION); + headers.add(HttpHeaders.CONTENT_RANGE); + headers.add(HttpHeaders.CONTENT_TYPE); + RESPONSE_CONTENT_HEADERS = Collections.unmodifiableSet(headers); + } + private static final Log logger = HttpLogging.forLogName(AbstractErrorWebExceptionHandler.class); private final ApplicationContext applicationContext; @@ -293,11 +308,16 @@ private String formatRequest(ServerRequest request) { } private Mono write(ServerWebExchange exchange, ServerResponse response) { - // force content-type since writeTo won't overwrite response header values - exchange.getResponse().getHeaders().setContentType(response.headers().getContentType()); + clearResponseContentHeaders(exchange.getResponse().getHeaders()); return response.writeTo(exchange, new ResponseContext()); } + private void clearResponseContentHeaders(HttpHeaders headers) { + for (String contentHeader : RESPONSE_CONTENT_HEADERS) { + headers.remove(contentHeader); + } + } + private class ResponseContext implements ServerResponse.Context { @Override 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 9d9bbd4d411f..803fc799a97b 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 @@ -16,6 +16,10 @@ package org.springframework.boot.autoconfigure.web.reactive.error; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + import javax.validation.Valid; import org.hamcrest.Matchers; @@ -32,8 +36,10 @@ import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.testsupport.rule.OutputCapture; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.HeaderAssertions; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -54,6 +60,20 @@ */ public class DefaultErrorWebExceptionHandlerIntegrationTests { + private static final Set RESPONSE_CONTENT_HEADERS; + + static { + Set headers = new HashSet<>(); + headers.add(HttpHeaders.CONTENT_ENCODING); + headers.add(HttpHeaders.CONTENT_DISPOSITION); + headers.add(HttpHeaders.CONTENT_LANGUAGE); + headers.add(HttpHeaders.CONTENT_LENGTH); + headers.add(HttpHeaders.CONTENT_LOCATION); + headers.add(HttpHeaders.CONTENT_RANGE); + headers.add(HttpHeaders.CONTENT_TYPE); + RESPONSE_CONTENT_HEADERS = Collections.unmodifiableSet(headers); + } + @Rule public OutputCapture outputCapture = new OutputCapture(); @@ -256,6 +276,18 @@ public void invalidAcceptMediaType() { }); } + @Test + public void contentHeadersWasCleared() { + this.contextRunner.run((context) -> { + WebTestClient client = WebTestClient.bindToApplicationContext(context).build(); + HeaderAssertions headerAssertions = client.get().uri("/contentHeader").exchange().expectStatus() + .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectHeader(); + RESPONSE_CONTENT_HEADERS.stream() + .filter(h -> !h.equals(HttpHeaders.CONTENT_TYPE) && !h.equals(HttpHeaders.CONTENT_LENGTH)) + .forEach(headerAssertions::doesNotExist); + }); + } + @Configuration public static class Application { @@ -278,6 +310,15 @@ public Mono commit(ServerWebExchange exchange) { .then(Mono.error(new IllegalStateException("already committed!"))); } + @GetMapping("/contentHeader") + public Mono contentHeader(ServerWebExchange exchange) { + HttpHeaders headers = exchange.getResponse().getHeaders(); + for (String contentHeader : RESPONSE_CONTENT_HEADERS) { + headers.set(contentHeader, "value"); + } + throw new IllegalStateException("Expected!"); + } + @GetMapping("/html") public String htmlEscape() { throw new IllegalStateException("