Fix ResourceRegionEncoder and tests
Fix ResourceRegionEncoder so that it checks for resource existance before writing boundaries. Also defer data buffer allocation until necessary. Issue: SPR-17419
This commit is contained in:
parent
eac9e66c46
commit
51bb96db47
|
|
@ -86,8 +86,15 @@ public class ResourceRegionEncoder extends AbstractEncoder<ResourceRegion> {
|
||||||
Assert.notNull(elementType, "'elementType' must not be null");
|
Assert.notNull(elementType, "'elementType' must not be null");
|
||||||
|
|
||||||
if (inputStream instanceof Mono) {
|
if (inputStream instanceof Mono) {
|
||||||
return ((Mono<? extends ResourceRegion>) inputStream)
|
return Mono.from(inputStream)
|
||||||
.flatMapMany(region -> writeResourceRegion(region, bufferFactory, hints));
|
.flatMapMany(region -> {
|
||||||
|
if (!region.getResource().isReadable()) {
|
||||||
|
return Flux.error(new EncodingException("Resource " +
|
||||||
|
region.getResource() + " is not readable"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeResourceRegion(region, bufferFactory, hints);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
final String boundaryString = Hints.getRequiredHint(hints, BOUNDARY_STRING_HINT);
|
final String boundaryString = Hints.getRequiredHint(hints, BOUNDARY_STRING_HINT);
|
||||||
|
|
@ -95,23 +102,29 @@ public class ResourceRegionEncoder extends AbstractEncoder<ResourceRegion> {
|
||||||
byte[] contentType =
|
byte[] contentType =
|
||||||
(mimeType != null ? getAsciiBytes("Content-Type: " + mimeType + "\r\n") : new byte[0]);
|
(mimeType != null ? getAsciiBytes("Content-Type: " + mimeType + "\r\n") : new byte[0]);
|
||||||
|
|
||||||
Flux<DataBuffer> regions = Flux.from(inputStream).
|
return Flux.from(inputStream).
|
||||||
concatMap(region ->
|
concatMap(region -> {
|
||||||
Flux.concat(
|
if (!region.getResource().isReadable()) {
|
||||||
|
return Flux.error(new EncodingException("Resource " +
|
||||||
|
region.getResource() + " is not readable"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Flux.concat(
|
||||||
getRegionPrefix(bufferFactory, startBoundary, contentType, region),
|
getRegionPrefix(bufferFactory, startBoundary, contentType, region),
|
||||||
writeResourceRegion(region, bufferFactory, hints)
|
writeResourceRegion(region, bufferFactory, hints));
|
||||||
));
|
}
|
||||||
return Flux.concat(regions, getRegionSuffix(bufferFactory, boundaryString));
|
})
|
||||||
|
.concatWith(getRegionSuffix(bufferFactory, boundaryString));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Flux<DataBuffer> getRegionPrefix(DataBufferFactory bufferFactory, byte[] startBoundary,
|
private Flux<DataBuffer> getRegionPrefix(DataBufferFactory bufferFactory, byte[] startBoundary,
|
||||||
byte[] contentType, ResourceRegion region) {
|
byte[] contentType, ResourceRegion region) {
|
||||||
|
|
||||||
return Flux.just(
|
return Flux.defer(() -> Flux.just(
|
||||||
bufferFactory.allocateBuffer(startBoundary.length).write(startBoundary),
|
bufferFactory.allocateBuffer(startBoundary.length).write(startBoundary),
|
||||||
bufferFactory.allocateBuffer(contentType.length).write(contentType),
|
bufferFactory.allocateBuffer(contentType.length).write(contentType),
|
||||||
bufferFactory.wrap(ByteBuffer.wrap(getContentRangeHeader(region)))
|
bufferFactory.wrap(ByteBuffer.wrap(getContentRangeHeader(region))))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,7 +146,8 @@ public class ResourceRegionEncoder extends AbstractEncoder<ResourceRegion> {
|
||||||
|
|
||||||
private Flux<DataBuffer> getRegionSuffix(DataBufferFactory bufferFactory, String boundaryString) {
|
private Flux<DataBuffer> getRegionSuffix(DataBufferFactory bufferFactory, String boundaryString) {
|
||||||
byte[] endBoundary = getAsciiBytes("\r\n--" + boundaryString + "--");
|
byte[] endBoundary = getAsciiBytes("\r\n--" + boundaryString + "--");
|
||||||
return Flux.just(bufferFactory.allocateBuffer(endBoundary.length).write(endBoundary));
|
return Flux.defer(() -> Flux.just(
|
||||||
|
bufferFactory.allocateBuffer(endBoundary.length).write(endBoundary)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] getAsciiBytes(String in) {
|
private byte[] getAsciiBytes(String in) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -31,17 +31,11 @@ import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.core.io.buffer.AbstractDataBufferAllocatingTestCase;
|
import org.springframework.core.io.buffer.AbstractDataBufferAllocatingTestCase;
|
||||||
import org.springframework.core.io.buffer.DataBuffer;
|
import org.springframework.core.io.buffer.DataBuffer;
|
||||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
|
||||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
|
||||||
import org.springframework.core.io.buffer.support.DataBufferTestUtils;
|
|
||||||
import org.springframework.core.io.support.ResourceRegion;
|
import org.springframework.core.io.support.ResourceRegion;
|
||||||
import org.springframework.util.MimeType;
|
import org.springframework.util.MimeType;
|
||||||
import org.springframework.util.MimeTypeUtils;
|
import org.springframework.util.MimeTypeUtils;
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test cases for {@link ResourceRegionEncoder} class.
|
* Test cases for {@link ResourceRegionEncoder} class.
|
||||||
|
|
@ -55,7 +49,6 @@ public class ResourceRegionEncoderTests extends AbstractDataBufferAllocatingTest
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
this.encoder = new ResourceRegionEncoder();
|
this.encoder = new ResourceRegionEncoder();
|
||||||
this.bufferFactory = new DefaultDataBufferFactory();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -111,6 +104,30 @@ public class ResourceRegionEncoderTests extends AbstractDataBufferAllocatingTest
|
||||||
new ByteArrayResource(content.getBytes(StandardCharsets.UTF_8)));
|
new ByteArrayResource(content.getBytes(StandardCharsets.UTF_8)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nonExisting() {
|
||||||
|
Resource resource = new ClassPathResource("ResourceRegionEncoderTests.txt", getClass());
|
||||||
|
Resource nonExisting = new ClassPathResource("does not exist", getClass());
|
||||||
|
Flux<ResourceRegion> regions = Flux.just(
|
||||||
|
new ResourceRegion(resource, 0, 6),
|
||||||
|
new ResourceRegion(nonExisting, 0, 6));
|
||||||
|
|
||||||
|
String boundary = MimeTypeUtils.generateMultipartBoundaryString();
|
||||||
|
|
||||||
|
Flux<DataBuffer> result = this.encoder.encode(regions, this.bufferFactory,
|
||||||
|
ResolvableType.forClass(ResourceRegion.class),
|
||||||
|
MimeType.valueOf("text/plain"),
|
||||||
|
Collections.singletonMap(ResourceRegionEncoder.BOUNDARY_STRING_HINT, boundary));
|
||||||
|
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.consumeNextWith(stringConsumer("\r\n--" + boundary + "\r\n"))
|
||||||
|
.consumeNextWith(stringConsumer("Content-Type: text/plain\r\n"))
|
||||||
|
.consumeNextWith(stringConsumer("Content-Range: bytes 0-5/39\r\n\r\n"))
|
||||||
|
.consumeNextWith(stringConsumer("Spring"))
|
||||||
|
.expectError(EncodingException.class)
|
||||||
|
.verify();
|
||||||
|
}
|
||||||
|
|
||||||
private void shouldEncodeMultipleResourceRegions(Resource resource) {
|
private void shouldEncodeMultipleResourceRegions(Resource resource) {
|
||||||
Flux<ResourceRegion> regions = Flux.just(
|
Flux<ResourceRegion> regions = Flux.just(
|
||||||
new ResourceRegion(resource, 0, 6),
|
new ResourceRegion(resource, 0, 6),
|
||||||
|
|
@ -120,45 +137,30 @@ public class ResourceRegionEncoderTests extends AbstractDataBufferAllocatingTest
|
||||||
);
|
);
|
||||||
String boundary = MimeTypeUtils.generateMultipartBoundaryString();
|
String boundary = MimeTypeUtils.generateMultipartBoundaryString();
|
||||||
|
|
||||||
Flux<DataBuffer> result = this.encoder.encode(regions, this.bufferFactory,
|
Flux<DataBuffer> result = this.encoder.encode(regions, super.bufferFactory,
|
||||||
ResolvableType.forClass(ResourceRegion.class),
|
ResolvableType.forClass(ResourceRegion.class),
|
||||||
MimeType.valueOf("text/plain"),
|
MimeType.valueOf("text/plain"),
|
||||||
Collections.singletonMap(ResourceRegionEncoder.BOUNDARY_STRING_HINT, boundary)
|
Collections.singletonMap(ResourceRegionEncoder.BOUNDARY_STRING_HINT, boundary)
|
||||||
);
|
);
|
||||||
|
|
||||||
Mono<DataBuffer> reduced = result
|
StepVerifier.create(result)
|
||||||
.reduce(bufferFactory.allocateBuffer(), (previous, current) -> {
|
.consumeNextWith(stringConsumer("\r\n--" + boundary + "\r\n"))
|
||||||
previous.write(current);
|
.consumeNextWith(stringConsumer("Content-Type: text/plain\r\n"))
|
||||||
DataBufferUtils.release(current);
|
.consumeNextWith(stringConsumer("Content-Range: bytes 0-5/39\r\n\r\n"))
|
||||||
return previous;
|
.consumeNextWith(stringConsumer("Spring"))
|
||||||
});
|
.consumeNextWith(stringConsumer("\r\n--" + boundary + "\r\n"))
|
||||||
|
.consumeNextWith(stringConsumer("Content-Type: text/plain\r\n"))
|
||||||
StepVerifier.create(reduced)
|
.consumeNextWith(stringConsumer("Content-Range: bytes 7-15/39\r\n\r\n"))
|
||||||
.consumeNextWith(buf -> {
|
.consumeNextWith(stringConsumer("Framework"))
|
||||||
String content = DataBufferTestUtils.dumpString(buf, StandardCharsets.UTF_8);
|
.consumeNextWith(stringConsumer("\r\n--" + boundary + "\r\n"))
|
||||||
String[] ranges = StringUtils.tokenizeToStringArray(content, "\r\n",
|
.consumeNextWith(stringConsumer("Content-Type: text/plain\r\n"))
|
||||||
false, true);
|
.consumeNextWith(stringConsumer("Content-Range: bytes 17-20/39\r\n\r\n"))
|
||||||
String[] expected = new String[] {
|
.consumeNextWith(stringConsumer("test"))
|
||||||
"--" + boundary,
|
.consumeNextWith(stringConsumer("\r\n--" + boundary + "\r\n"))
|
||||||
"Content-Type: text/plain",
|
.consumeNextWith(stringConsumer("Content-Type: text/plain\r\n"))
|
||||||
"Content-Range: bytes 0-5/39",
|
.consumeNextWith(stringConsumer("Content-Range: bytes 22-38/39\r\n\r\n"))
|
||||||
"Spring",
|
.consumeNextWith(stringConsumer("resource content."))
|
||||||
"--" + boundary,
|
.consumeNextWith(stringConsumer("\r\n--" + boundary + "--"))
|
||||||
"Content-Type: text/plain",
|
|
||||||
"Content-Range: bytes 7-15/39",
|
|
||||||
"Framework",
|
|
||||||
"--" + boundary,
|
|
||||||
"Content-Type: text/plain",
|
|
||||||
"Content-Range: bytes 17-20/39",
|
|
||||||
"test",
|
|
||||||
"--" + boundary,
|
|
||||||
"Content-Type: text/plain",
|
|
||||||
"Content-Range: bytes 22-38/39",
|
|
||||||
"resource content.",
|
|
||||||
"--" + boundary + "--"
|
|
||||||
};
|
|
||||||
assertArrayEquals(expected, ranges);
|
|
||||||
})
|
|
||||||
.expectComplete()
|
.expectComplete()
|
||||||
.verify();
|
.verify();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue