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
This commit is contained in:
parent
ce5c65c0b0
commit
3203d39821
|
|
@ -118,14 +118,12 @@ public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
|
|||
|
||||
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) ?
|
||||
|
|
|
|||
|
|
@ -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<Void> writeWith(Publisher<? extends DataBuffer> body) {
|
||||
return new ChannelSendOperator<>(body,
|
||||
writePublisher -> doCommit(() -> writeWithInternal(writePublisher)));
|
||||
writePublisher -> doCommit(() -> writeWithInternal(writePublisher)))
|
||||
.doOnError(t -> removeContentLength());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> 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
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue