Skip to content

Commit c735ffb

Browse files
committed
Fix content type issue in ResourceRegionHttpMessageWriter
ResourceRegionHttpMessageWriter no longer extends from EncoderHttpMessageWriter freeing it to pass the correct content type into the encoder. Considering that the main benefit of EncoderHttpMessageWriter is to deal with content type fallback cases, there is nothing to be missed. Furthermore ResourceRegionHttpMessageWriter is a package private class that is used internally within ResourceHttpMessageWriter and never exposed externally as a an actual HttpMessageWriter. Issue: SPR-15358
1 parent 2979b37 commit c735ffb

File tree

4 files changed

+44
-37
lines changed

4 files changed

+44
-37
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,9 @@ public Mono<Void> write(Publisher<? extends Resource> inputStream, ResolvableTyp
119119
.flatMap(resource -> Flux.fromIterable(HttpRange.toResourceRegions(httpRanges, resource)));
120120

121121
return this.resourceRegionHttpMessageWriter
122-
.write(regions, ResolvableType.forClass(ResourceRegion.class), mediaType, response, mergedHints);
122+
.writeRegions(regions, mediaType, response, mergedHints);
123123
}
124-
else {
124+
else {
125125
return write(inputStream, elementType, mediaType, response, mergedHints);
126126
}
127127
}

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

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,54 +24,66 @@
2424
import java.util.OptionalLong;
2525

2626
import org.reactivestreams.Publisher;
27+
import reactor.core.publisher.Flux;
2728
import reactor.core.publisher.Mono;
2829

2930
import org.springframework.core.ResolvableType;
3031
import org.springframework.core.codec.ResourceRegionEncoder;
3132
import org.springframework.core.io.InputStreamResource;
3233
import org.springframework.core.io.Resource;
34+
import org.springframework.core.io.buffer.DataBuffer;
35+
import org.springframework.core.io.buffer.DataBufferFactory;
3336
import org.springframework.core.io.support.ResourceRegion;
3437
import org.springframework.http.MediaType;
3538
import org.springframework.http.ReactiveHttpOutputMessage;
3639
import org.springframework.http.ZeroCopyHttpOutputMessage;
3740

3841
/**
39-
* Implementation of {@link HttpMessageWriter} that can write
40-
* {@link ResourceRegion ResourceRegion}s.
41-
*
42-
* <p>Note that there is no {@link HttpMessageReader} counterpart.
42+
* Package private helper for {@link ResourceHttpMessageWriter} to assist with
43+
* writing {@link ResourceRegion ResourceRegion}s.
4344
*
4445
* @author Brian Clozel
46+
* @author Rossen Stoyanchev
4547
* @since 5.0
4648
*/
47-
class ResourceRegionHttpMessageWriter extends EncoderHttpMessageWriter<ResourceRegion> {
49+
class ResourceRegionHttpMessageWriter {
4850

4951
public static final String BOUNDARY_STRING_HINT = ResourceRegionHttpMessageWriter.class.getName() + ".boundaryString";
5052

53+
private static final ResolvableType TYPE = ResolvableType.forClass(ResourceRegion.class);
54+
55+
56+
private final ResourceRegionEncoder encoder;
57+
58+
5159
public ResourceRegionHttpMessageWriter() {
52-
super(new ResourceRegionEncoder());
60+
this.encoder = new ResourceRegionEncoder();
5361
}
5462

5563
public ResourceRegionHttpMessageWriter(int bufferSize) {
56-
super(new ResourceRegionEncoder(bufferSize));
64+
this.encoder = new ResourceRegionEncoder(bufferSize);
5765
}
5866

59-
@Override
60-
public Mono<Void> write(Publisher<? extends ResourceRegion> inputStream, ResolvableType type,
61-
MediaType contentType, ReactiveHttpOutputMessage outputMessage, Map<String, Object> hints) {
67+
68+
public Mono<Void> writeRegions(Publisher<? extends ResourceRegion> inputStream, MediaType contentType,
69+
ReactiveHttpOutputMessage outputMessage, Map<String, Object> hints) {
6270

6371
if (hints != null && hints.containsKey(BOUNDARY_STRING_HINT)) {
6472
String boundary = (String) hints.get(BOUNDARY_STRING_HINT);
6573
hints.put(ResourceRegionEncoder.BOUNDARY_STRING_HINT, boundary);
66-
outputMessage.getHeaders()
67-
.setContentType(MediaType.parseMediaType("multipart/byteranges;boundary=" + boundary));
68-
return super.write(inputStream, type, contentType, outputMessage, hints);
74+
75+
MediaType multipartType = MediaType.parseMediaType("multipart/byteranges;boundary=" + boundary);
76+
outputMessage.getHeaders().setContentType(multipartType);
77+
78+
DataBufferFactory bufferFactory = outputMessage.bufferFactory();
79+
Flux<DataBuffer> body = this.encoder.encode(inputStream, bufferFactory, TYPE, contentType, hints);
80+
return outputMessage.writeWith(body);
6981
}
7082
else {
7183
return Mono.from(inputStream)
7284
.then(region -> {
7385
writeSingleResourceRegionHeaders(region, contentType, outputMessage);
74-
return writeResourceRegion(region, type, outputMessage);
86+
return writeResourceRegion(region, outputMessage);
7587
});
7688
}
7789
}
@@ -109,8 +121,8 @@ private OptionalLong contentLength(Resource resource) {
109121
return OptionalLong.empty();
110122
}
111123

112-
private Mono<Void> writeResourceRegion(ResourceRegion region,
113-
ResolvableType type, ReactiveHttpOutputMessage outputMessage) {
124+
private Mono<Void> writeResourceRegion(ResourceRegion region, ReactiveHttpOutputMessage outputMessage) {
125+
114126
if (outputMessage instanceof ZeroCopyHttpOutputMessage) {
115127
Optional<File> file = getFile(region.getResource());
116128
if (file.isPresent()) {
@@ -122,8 +134,13 @@ private Mono<Void> writeResourceRegion(ResourceRegion region,
122134
}
123135

124136
// non-zero copy fallback, using ResourceRegionEncoder
125-
return super.write(Mono.just(region), type,
126-
outputMessage.getHeaders().getContentType(), outputMessage, Collections.emptyMap());
137+
138+
DataBufferFactory bufferFactory = outputMessage.bufferFactory();
139+
MediaType contentType = outputMessage.getHeaders().getContentType();
140+
Map<String, Object> hints = Collections.emptyMap();
141+
142+
Flux<DataBuffer> body = this.encoder.encode(Mono.just(region), bufferFactory, TYPE, contentType, hints);
143+
return outputMessage.writeWith(body);
127144
}
128145

129146
private static Optional<File> getFile(Resource resource) {

spring-web/src/test/java/org/springframework/http/codec/ResourceRegionHttpMessageWriterTests.java

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,13 @@
2323
import java.util.Map;
2424

2525
import org.junit.Before;
26-
import org.junit.Ignore;
2726
import org.junit.Rule;
2827
import org.junit.Test;
2928
import org.junit.rules.ExpectedException;
3029
import reactor.core.publisher.Flux;
3130
import reactor.core.publisher.Mono;
3231
import reactor.test.StepVerifier;
3332

34-
import org.springframework.core.ResolvableType;
3533
import org.springframework.core.io.ByteArrayResource;
3634
import org.springframework.core.io.Resource;
3735
import org.springframework.core.io.support.ResourceRegion;
@@ -41,8 +39,10 @@
4139
import org.springframework.util.MimeTypeUtils;
4240
import org.springframework.util.StringUtils;
4341

44-
import static org.hamcrest.Matchers.*;
45-
import static org.junit.Assert.*;
42+
import static org.hamcrest.Matchers.is;
43+
import static org.hamcrest.Matchers.startsWith;
44+
import static org.junit.Assert.assertArrayEquals;
45+
import static org.junit.Assert.assertThat;
4646

4747
/**
4848
* Unit tests for {@link ResourceRegionHttpMessageWriter}.
@@ -67,19 +67,13 @@ public void setUp() throws Exception {
6767
}
6868

6969

70-
@Test
71-
public void writableMediaTypes() throws Exception {
72-
assertThat(this.writer.getWritableMediaTypes(),
73-
containsInAnyOrder(MimeTypeUtils.APPLICATION_OCTET_STREAM, MimeTypeUtils.ALL));
74-
}
75-
7670
@Test
7771
public void shouldWriteResourceRegion() throws Exception {
7872

7973
ResourceRegion region = new ResourceRegion(this.resource, 0, 6);
74+
Map<String, Object> hints = Collections.emptyMap();
8075

81-
Mono<Void> mono = this.writer.write(Mono.just(region), ResolvableType.forClass(ResourceRegion.class),
82-
MediaType.TEXT_PLAIN, this.response, Collections.emptyMap());
76+
Mono<Void> mono = this.writer.writeRegions(Mono.just(region), MediaType.TEXT_PLAIN, this.response, hints);
8377
StepVerifier.create(mono).expectComplete().verify();
8478

8579
assertThat(this.response.getHeaders().getContentType(), is(MediaType.TEXT_PLAIN));
@@ -91,7 +85,6 @@ public void shouldWriteResourceRegion() throws Exception {
9185
}
9286

9387
@Test
94-
@Ignore("Until issue resolved: ResourceRegion should not use response content-type")
9588
public void shouldWriteMultipleResourceRegions() throws Exception {
9689
Flux<ResourceRegion> regions = Flux.just(
9790
new ResourceRegion(this.resource, 0, 6),
@@ -103,8 +96,7 @@ public void shouldWriteMultipleResourceRegions() throws Exception {
10396
Map<String, Object> hints = new HashMap<>(1);
10497
hints.put(ResourceRegionHttpMessageWriter.BOUNDARY_STRING_HINT, boundary);
10598

106-
Mono<Void> mono = this.writer.write(regions, ResolvableType.forClass(ResourceRegion.class),
107-
MediaType.TEXT_PLAIN, this.response, hints);
99+
Mono<Void> mono = this.writer.writeRegions(regions, MediaType.TEXT_PLAIN, this.response, hints);
108100
StepVerifier.create(mono).expectComplete().verify();
109101

110102
HttpHeaders headers = this.response.getHeaders();

spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import java.util.concurrent.TimeUnit;
2727

2828
import org.junit.Before;
29-
import org.junit.Ignore;
3029
import org.junit.Test;
3130
import reactor.core.publisher.Flux;
3231
import reactor.core.publisher.Mono;
@@ -538,7 +537,6 @@ public void partialContentInvalidRangeHeader() throws Exception {
538537
}
539538

540539
@Test
541-
@Ignore("Until issue resolved: ResourceRegion should not use response content-type")
542540
public void partialContentMultipleByteRanges() throws Exception {
543541
MockServerWebExchange exchange = MockServerHttpRequest.get("").header("Range", "bytes=0-1, 4-5, 8-9").toExchange();
544542
setPathWithinHandlerMapping(exchange, "foo.txt");

0 commit comments

Comments
 (0)