diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/HttpHeadResponseDecorator.java b/spring-web/src/main/java/org/springframework/http/server/reactive/HttpHeadResponseDecorator.java index d387abd329..b52a38f722 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/HttpHeadResponseDecorator.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/HttpHeadResponseDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-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. @@ -16,8 +16,6 @@ package org.springframework.http.server.reactive; -import java.util.function.BiFunction; - import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -41,24 +39,32 @@ public class HttpHeadResponseDecorator extends ServerHttpResponseDecorator { /** - * Apply {@link Flux#reduce(Object, BiFunction) reduce} on the body, count - * the number of bytes produced, release data buffers without writing, and - * set the {@literal Content-Length} header. + * Consume and release the body without writing. + *

If the headers contain neither Content-Length nor Transfer-Encoding, + * count the bytes and set Content-Length. */ @Override public final Mono writeWith(Publisher body) { - return Flux.from(body) - .reduce(0, (current, buffer) -> { - int next = current + buffer.readableByteCount(); - DataBufferUtils.release(buffer); - return next; - }) - .doOnNext(length -> { - if (length > 0 || getHeaders().getFirst(HttpHeaders.CONTENT_LENGTH) == null) { - getHeaders().setContentLength(length); - } - }) - .then(); + if (shouldSetContentLength()) { + return Flux.from(body) + .reduce(0, (current, buffer) -> { + int next = current + buffer.readableByteCount(); + DataBufferUtils.release(buffer); + return next; + }) + .doOnNext(length -> getHeaders().setContentLength(length)) + .then(); + } + else { + return Flux.from(body) + .doOnNext(DataBufferUtils::release) + .then(); + } + } + + private boolean shouldSetContentLength() { + return (getHeaders().getFirst(HttpHeaders.CONTENT_LENGTH) == null && + getHeaders().getFirst(HttpHeaders.TRANSFER_ENCODING) == null); } /** diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/HttpHeadResponseDecoratorTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/HttpHeadResponseDecoratorTests.java index a3d32c5f13..d98b19356f 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/HttpHeadResponseDecoratorTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/HttpHeadResponseDecoratorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-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. @@ -25,6 +25,7 @@ import reactor.core.publisher.Flux; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.NettyDataBufferFactory; import org.springframework.core.testfixture.io.buffer.LeakAwareDataBufferFactory; +import org.springframework.http.HttpHeaders; import org.springframework.web.testfixture.http.server.reactive.MockServerHttpResponse; import static org.assertj.core.api.Assertions.assertThat; @@ -63,6 +64,13 @@ public class HttpHeadResponseDecoratorTests { assertThat(this.response.getHeaders().getContentLength()).isEqualTo(length); } + @Test // gh-25908 + public void writeWithGivenTransferEncoding() { + Flux body = Flux.just(toDataBuffer("data1"), toDataBuffer("data2")); + this.response.getHeaders().add(HttpHeaders.TRANSFER_ENCODING, "chunked"); + this.response.writeWith(body).block(); + assertThat(this.response.getHeaders().getContentLength()).isEqualTo(-1); + } private DataBuffer toDataBuffer(String s) { DataBuffer buffer = this.bufferFactory.allocateBuffer();