From 3203d39821dc25e4a481697d5a298f0303d0a3e0 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Mon, 19 Nov 2018 15:33:06 +0100 Subject: [PATCH] Remove Content-Length response header from errors Prior to this commit, when errors happened before the response was committed, the `Content-Length` response header would be left as is. This can be problematic since the error can be handled later in the chain and the response body changed accordingly. For example, Spring Boot renders error pages in those cases. If the `Content-Length` is set, HTTP clients can get confused and only consider part of the error response body. This commit ensures that any `Content-Length` response header is removed in case of errors, if the response is not already committed. This is done at the `AbstractServerHttpResponse` level, since errors can be handled in multiple places and the response itself is the safest place to handle this case. As a consequence, this commit also removes `Content-Length` checks in `EncoderHttpMessageWriter` since we now consider that we should rely on the response body we're about to write rather than any previously set value. Issue: SPR-17502 --- .../http/codec/EncoderHttpMessageWriter.java | 14 ++++++-------- .../reactive/AbstractServerHttpResponse.java | 13 +++++++++++-- .../server/reactive/ServerHttpResponseTests.java | 3 +++ 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java index aa091045a8..03aab3dcb6 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java @@ -118,14 +118,12 @@ public class EncoderHttpMessageWriter implements HttpMessageWriter { if (inputStream instanceof Mono) { HttpHeaders headers = message.getHeaders(); - if (headers.getFirst(HttpHeaders.CONTENT_LENGTH) == null) { - return Mono.from(body) - .defaultIfEmpty(message.bufferFactory().wrap(new byte[0])) - .flatMap(buffer -> { - headers.setContentLength(buffer.readableByteCount()); - return message.writeWith(Mono.just(buffer)); - }); - } + return Mono.from(body) + .defaultIfEmpty(message.bufferFactory().wrap(new byte[0])) + .flatMap(buffer -> { + headers.setContentLength(buffer.readableByteCount()); + return message.writeWith(Mono.just(buffer)); + }); } return (isStreamingMediaType(contentType) ? diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpResponse.java index ab396dc920..8be6b1899b 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpResponse.java @@ -45,6 +45,7 @@ import org.springframework.util.MultiValueMap; * @author Rossen Stoyanchev * @author Juergen Hoeller * @author Sebastien Deleuze + * @author Brian Clozel * @since 5.0 */ public abstract class AbstractServerHttpResponse implements ServerHttpResponse { @@ -174,13 +175,21 @@ public abstract class AbstractServerHttpResponse implements ServerHttpResponse { @Override public final Mono writeWith(Publisher body) { return new ChannelSendOperator<>(body, - writePublisher -> doCommit(() -> writeWithInternal(writePublisher))); + writePublisher -> doCommit(() -> writeWithInternal(writePublisher))) + .doOnError(t -> removeContentLength()); } @Override public final Mono writeAndFlushWith(Publisher> body) { return new ChannelSendOperator<>(body, - writePublisher -> doCommit(() -> writeAndFlushWithInternal(writePublisher))); + writePublisher -> doCommit(() -> writeAndFlushWithInternal(writePublisher))) + .doOnError(t -> removeContentLength()); + } + + private void removeContentLength() { + if (!this.isCommitted()) { + this.getHeaders().remove(HttpHeaders.CONTENT_LENGTH); + } } @Override diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpResponseTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpResponseTests.java index 651e9c55bd..55746952ca 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpResponseTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpResponseTests.java @@ -29,6 +29,7 @@ import reactor.core.publisher.Mono; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DefaultDataBuffer; import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseCookie; import static junit.framework.TestCase.assertTrue; @@ -75,12 +76,14 @@ public class ServerHttpResponseTests { @Test public void writeWithError() throws Exception { TestServerHttpResponse response = new TestServerHttpResponse(); + response.getHeaders().setContentLength(12); IllegalStateException error = new IllegalStateException("boo"); response.writeWith(Flux.error(error)).onErrorResume(ex -> Mono.empty()).block(); assertFalse(response.statusCodeWritten); assertFalse(response.headersWritten); assertFalse(response.cookiesWritten); + assertFalse(response.getHeaders().containsKey(HttpHeaders.CONTENT_LENGTH)); assertTrue(response.body.isEmpty()); }