Skip to content

Commit 0103521

Browse files
committed
Eliminate the need for Encoder#getContentLength
Issue: SPR-16892
1 parent 124d4c8 commit 0103521

File tree

9 files changed

+55
-72
lines changed

9 files changed

+55
-72
lines changed

spring-core/src/main/java/org/springframework/core/codec/ByteArrayEncoder.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,4 @@ public Flux<DataBuffer> encode(Publisher<? extends byte[]> inputStream,
5555
return Flux.from(inputStream).map(bufferFactory::wrap);
5656
}
5757

58-
@Override
59-
public Long getContentLength(byte[] bytes, @Nullable MimeType mimeType) {
60-
return (long) bytes.length;
61-
}
6258
}

spring-core/src/main/java/org/springframework/core/codec/ByteBufferEncoder.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,4 @@ public Flux<DataBuffer> encode(Publisher<? extends ByteBuffer> inputStream,
5656
return Flux.from(inputStream).map(bufferFactory::wrap);
5757
}
5858

59-
@Override
60-
public Long getContentLength(ByteBuffer byteBuffer, @Nullable MimeType mimeType) {
61-
return (long) byteBuffer.array().length;
62-
}
6359
}

spring-core/src/main/java/org/springframework/core/codec/CharSequenceEncoder.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,6 @@ private Charset getCharset(@Nullable MimeType mimeType) {
8282
return charset;
8383
}
8484

85-
@Override
86-
public Long getContentLength(CharSequence data, @Nullable MimeType mimeType) {
87-
return (long) data.toString().getBytes(getCharset(mimeType)).length;
88-
}
89-
9085
/**
9186
* Create a {@code CharSequenceEncoder} that supports only "text/plain".
9287
*/

spring-core/src/main/java/org/springframework/core/codec/DataBufferEncoder.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,4 @@ public Flux<DataBuffer> encode(Publisher<? extends DataBuffer> inputStream,
5555
return Flux.from(inputStream);
5656
}
5757

58-
@Override
59-
public Long getContentLength(DataBuffer dataBuffer, @Nullable MimeType mimeType) {
60-
return (long) dataBuffer.readableByteCount();
61-
}
62-
6358
}

spring-core/src/main/java/org/springframework/core/codec/Encoder.java

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,6 @@ public interface Encoder<T> {
6767
Flux<DataBuffer> encode(Publisher<? extends T> inputStream, DataBufferFactory bufferFactory,
6868
ResolvableType elementType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints);
6969

70-
/**
71-
* Return the length for the given item, if known.
72-
* @param t the item to check
73-
* @return the length in bytes, or {@code null} if not known.
74-
* @since 5.0.5
75-
*/
76-
@Nullable
77-
default Long getContentLength(T t, @Nullable MimeType mimeType) {
78-
return null;
79-
}
80-
8170
/**
8271
* Return the list of mime types this encoder supports.
8372
*/

spring-core/src/main/java/org/springframework/core/codec/ResourceEncoder.java

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,11 @@
1616

1717
package org.springframework.core.codec;
1818

19-
import java.io.IOException;
2019
import java.util.Map;
2120

2221
import reactor.core.publisher.Flux;
2322

2423
import org.springframework.core.ResolvableType;
25-
import org.springframework.core.io.InputStreamResource;
2624
import org.springframework.core.io.Resource;
2725
import org.springframework.core.io.buffer.DataBuffer;
2826
import org.springframework.core.io.buffer.DataBufferFactory;
@@ -70,17 +68,4 @@ protected Flux<DataBuffer> encode(Resource resource, DataBufferFactory dataBuffe
7068
return DataBufferUtils.read(resource, dataBufferFactory, this.bufferSize);
7169
}
7270

73-
@Override
74-
public Long getContentLength(Resource resource, @Nullable MimeType mimeType) {
75-
// Don't consume InputStream...
76-
if (InputStreamResource.class != resource.getClass()) {
77-
try {
78-
return resource.contentLength();
79-
}
80-
catch (IOException ignored) {
81-
}
82-
}
83-
return null;
84-
}
85-
8671
}

spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.core.ResolvableType;
2929
import org.springframework.core.codec.Encoder;
3030
import org.springframework.core.io.buffer.DataBuffer;
31+
import org.springframework.core.io.buffer.DataBufferFactory;
3132
import org.springframework.http.HttpHeaders;
3233
import org.springframework.http.MediaType;
3334
import org.springframework.http.ReactiveHttpOutputMessage;
@@ -98,23 +99,18 @@ public Mono<Void> write(Publisher<? extends T> inputStream, ResolvableType eleme
9899
@Nullable MediaType mediaType, ReactiveHttpOutputMessage message, Map<String, Object> hints) {
99100

100101
MediaType contentType = updateContentType(message, mediaType);
101-
HttpHeaders headers = message.getHeaders();
102-
103-
if (headers.getContentLength() < 0 && !headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
104-
if (inputStream instanceof Mono) {
105-
// This works because we don't actually commit until after the first signal...
106-
inputStream = ((Mono<T>) inputStream).doOnNext(data -> {
107-
Long contentLength = this.encoder.getContentLength(data, contentType);
108-
if (contentLength != null) {
109-
headers.setContentLength(contentLength);
110-
}
111-
});
112-
}
113-
}
114102

115103
Flux<DataBuffer> body = this.encoder.encode(
116104
inputStream, message.bufferFactory(), elementType, contentType, hints);
117105

106+
// Response is not committed until the first signal...
107+
if (inputStream instanceof Mono) {
108+
HttpHeaders headers = message.getHeaders();
109+
if (headers.getContentLength() < 0 && !headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
110+
body = body.doOnNext(data -> headers.setContentLength(data.readableByteCount()));
111+
}
112+
}
113+
118114
return (isStreamingMediaType(contentType) ?
119115
message.writeAndFlushWith(body.map(Flux::just)) : message.writeWith(body));
120116
}

spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.core.codec.ResourceDecoder;
3232
import org.springframework.core.codec.ResourceEncoder;
3333
import org.springframework.core.codec.ResourceRegionEncoder;
34+
import org.springframework.core.io.InputStreamResource;
3435
import org.springframework.core.io.Resource;
3536
import org.springframework.core.io.buffer.DataBuffer;
3637
import org.springframework.core.io.buffer.DataBufferFactory;
@@ -119,9 +120,9 @@ private Mono<Void> writeResource(Resource resource, ResolvableType type, @Nullab
119120
headers.setContentType(resourceMediaType);
120121

121122
if (headers.getContentLength() < 0) {
122-
Long contentLength = this.encoder.getContentLength(resource, mediaType);
123-
if (contentLength != null) {
124-
headers.setContentLength(contentLength);
123+
long length = lengthOf(resource);
124+
if (length != -1) {
125+
headers.setContentLength(length);
125126
}
126127
}
127128

@@ -141,6 +142,18 @@ private static MediaType getResourceMediaType(@Nullable MediaType mediaType, Res
141142
return MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM);
142143
}
143144

145+
private static long lengthOf(Resource resource) {
146+
// Don't consume InputStream...
147+
if (InputStreamResource.class != resource.getClass()) {
148+
try {
149+
return resource.contentLength();
150+
}
151+
catch (IOException ignored) {
152+
}
153+
}
154+
return -1;
155+
}
156+
144157
private static Optional<Mono<Void>> zeroCopy(Resource resource, @Nullable ResourceRegion region,
145158
ReactiveHttpOutputMessage message) {
146159

@@ -192,8 +205,8 @@ public Mono<Void> write(Publisher<? extends Resource> inputStream, @Nullable Res
192205
if (regions.size() == 1){
193206
ResourceRegion region = regions.get(0);
194207
headers.setContentType(resourceMediaType);
195-
Long contentLength = this.encoder.getContentLength(resource, mediaType);
196-
if (contentLength != null) {
208+
long contentLength = lengthOf(resource);
209+
if (contentLength != -1) {
197210
long start = region.getPosition();
198211
long end = start + region.getCount() - 1;
199212
end = Math.min(end, contentLength - 1);

spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -128,59 +128,77 @@ public void byteBufferResponseBodyWithFlowable() throws Exception {
128128
@Test
129129
public void personResponseBody() throws Exception {
130130
Person expected = new Person("Robert");
131-
assertEquals(expected, performGet("/person-response/person", JSON, Person.class).getBody());
131+
ResponseEntity<Person> responseEntity = performGet("/person-response/person", JSON, Person.class);
132+
assertEquals(17, responseEntity.getHeaders().getContentLength());
133+
assertEquals(expected, responseEntity.getBody());
132134
}
133135

134136
@Test
135137
public void personResponseBodyWithCompletableFuture() throws Exception {
136138
Person expected = new Person("Robert");
137-
assertEquals(expected, performGet("/person-response/completable-future", JSON, Person.class).getBody());
139+
ResponseEntity<Person> responseEntity = performGet("/person-response/completable-future", JSON, Person.class);
140+
assertEquals(17, responseEntity.getHeaders().getContentLength());
141+
assertEquals(expected, responseEntity.getBody());
138142
}
139143

140144
@Test
141145
public void personResponseBodyWithMono() throws Exception {
142146
Person expected = new Person("Robert");
143-
assertEquals(expected, performGet("/person-response/mono", JSON, Person.class).getBody());
147+
ResponseEntity<Person> responseEntity = performGet("/person-response/mono", JSON, Person.class);
148+
assertEquals(17, responseEntity.getHeaders().getContentLength());
149+
assertEquals(expected, responseEntity.getBody());
144150
}
145151

146152
@Test
147153
public void personResponseBodyWithMonoDeclaredAsObject() throws Exception {
148154
Person expected = new Person("Robert");
149-
assertEquals(expected, performGet("/person-response/mono-declared-as-object", JSON, Person.class).getBody());
155+
ResponseEntity<Person> entity = performGet("/person-response/mono-declared-as-object", JSON, Person.class);
156+
assertEquals(17, entity.getHeaders().getContentLength());
157+
assertEquals(expected, entity.getBody());
150158
}
151159

152160
@Test
153161
public void personResponseBodyWithSingle() throws Exception {
154162
Person expected = new Person("Robert");
155-
assertEquals(expected, performGet("/person-response/single", JSON, Person.class).getBody());
163+
ResponseEntity<Person> entity = performGet("/person-response/single", JSON, Person.class);
164+
assertEquals(17, entity.getHeaders().getContentLength());
165+
assertEquals(expected, entity.getBody());
156166
}
157167

158168
@Test
159169
public void personResponseBodyWithMonoResponseEntity() throws Exception {
160170
Person expected = new Person("Robert");
161-
assertEquals(expected, performGet("/person-response/mono-response-entity", JSON, Person.class).getBody());
171+
ResponseEntity<Person> entity = performGet("/person-response/mono-response-entity", JSON, Person.class);
172+
assertEquals(17, entity.getHeaders().getContentLength());
173+
assertEquals(expected, entity.getBody());
162174
}
163175

164176
@Test // SPR-16172
165177
public void personResponseBodyWithMonoResponseEntityXml() throws Exception {
166178

167-
String actual = performGet("/person-response/mono-response-entity-xml",
168-
new HttpHeaders(), String.class).getBody();
179+
String url = "/person-response/mono-response-entity-xml";
180+
ResponseEntity<String> entity = performGet(url, new HttpHeaders(), String.class);
181+
String actual = entity.getBody();
169182

183+
assertEquals(91, entity.getHeaders().getContentLength());
170184
assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
171185
"<person><name>Robert</name></person>", actual);
172186
}
173187

174188
@Test
175189
public void personResponseBodyWithList() throws Exception {
176190
List<?> expected = asList(new Person("Robert"), new Person("Marie"));
177-
assertEquals(expected, performGet("/person-response/list", JSON, PERSON_LIST).getBody());
191+
ResponseEntity<List<Person>> entity = performGet("/person-response/list", JSON, PERSON_LIST);
192+
assertEquals(36, entity.getHeaders().getContentLength());
193+
assertEquals(expected, entity.getBody());
178194
}
179195

180196
@Test
181197
public void personResponseBodyWithPublisher() throws Exception {
182198
List<?> expected = asList(new Person("Robert"), new Person("Marie"));
183-
assertEquals(expected, performGet("/person-response/publisher", JSON, PERSON_LIST).getBody());
199+
ResponseEntity<List<Person>> entity = performGet("/person-response/publisher", JSON, PERSON_LIST);
200+
assertEquals(-1, entity.getHeaders().getContentLength());
201+
assertEquals(expected, entity.getBody());
184202
}
185203

186204
@Test

0 commit comments

Comments
 (0)