下面列出了怎么用org.springframework.core.io.support.ResourceRegion的API类实例代码及写法,或者点击链接到github查看源代码。
@Test // gh-22107
public void cancelWithoutDemandForMultipleResourceRegions() {
Resource resource = new ClassPathResource("ResourceRegionEncoderTests.txt", getClass());
Flux<ResourceRegion> regions = Flux.just(
new ResourceRegion(resource, 0, 6),
new ResourceRegion(resource, 7, 9),
new ResourceRegion(resource, 17, 4),
new ResourceRegion(resource, 22, 17)
);
String boundary = MimeTypeUtils.generateMultipartBoundaryString();
Flux<DataBuffer> flux = this.encoder.encode(regions, this.bufferFactory,
ResolvableType.forClass(ResourceRegion.class),
MimeType.valueOf("text/plain"),
Collections.singletonMap(ResourceRegionEncoder.BOUNDARY_STRING_HINT, boundary)
);
ZeroDemandSubscriber subscriber = new ZeroDemandSubscriber();
flux.subscribe(subscriber);
subscriber.cancel();
}
@Test // gh-22107
public void cancelWithoutDemandForSingleResourceRegion() {
Resource resource = new ClassPathResource("ResourceRegionEncoderTests.txt", getClass());
Mono<ResourceRegion> regions = Mono.just(new ResourceRegion(resource, 0, 6));
String boundary = MimeTypeUtils.generateMultipartBoundaryString();
Flux<DataBuffer> flux = this.encoder.encode(regions, this.bufferFactory,
ResolvableType.forClass(ResourceRegion.class),
MimeType.valueOf("text/plain"),
Collections.singletonMap(ResourceRegionEncoder.BOUNDARY_STRING_HINT, boundary)
);
ZeroDemandSubscriber subscriber = new ZeroDemandSubscriber();
flux.subscribe(subscriber);
subscriber.cancel();
}
@Override
public boolean canWrite(@Nullable Type type, @Nullable Class<?> clazz, @Nullable MediaType mediaType) {
if (!(type instanceof ParameterizedType)) {
return (type instanceof Class && ResourceRegion.class.isAssignableFrom((Class<?>) type));
}
ParameterizedType parameterizedType = (ParameterizedType) type;
if (!(parameterizedType.getRawType() instanceof Class)) {
return false;
}
Class<?> rawType = (Class<?>) parameterizedType.getRawType();
if (!(Collection.class.isAssignableFrom(rawType))) {
return false;
}
if (parameterizedType.getActualTypeArguments().length != 1) {
return false;
}
Type typeArgument = parameterizedType.getActualTypeArguments()[0];
if (!(typeArgument instanceof Class)) {
return false;
}
Class<?> typeArgumentClass = (Class<?>) typeArgument;
return ResourceRegion.class.isAssignableFrom(typeArgumentClass);
}
@Override
@SuppressWarnings("unchecked")
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
if (object instanceof ResourceRegion) {
writeResourceRegion((ResourceRegion) object, outputMessage);
}
else {
Collection<ResourceRegion> regions = (Collection<ResourceRegion>) object;
if (regions.size() == 1) {
writeResourceRegion(regions.iterator().next(), outputMessage);
}
else {
writeResourceRegionCollection((Collection<ResourceRegion>) object, outputMessage);
}
}
}
protected void writeResourceRegion(ResourceRegion region, HttpOutputMessage outputMessage) throws IOException {
Assert.notNull(region, "ResourceRegion must not be null");
HttpHeaders responseHeaders = outputMessage.getHeaders();
long start = region.getPosition();
long end = start + region.getCount() - 1;
Long resourceLength = region.getResource().contentLength();
end = Math.min(end, resourceLength - 1);
long rangeLength = end - start + 1;
responseHeaders.add("Content-Range", "bytes " + start + '-' + end + '/' + resourceLength);
responseHeaders.setContentLength(rangeLength);
InputStream in = region.getResource().getInputStream();
try {
StreamUtils.copyRange(in, outputMessage.getBody(), start, end);
}
finally {
try {
in.close();
}
catch (IOException ex) {
// ignore
}
}
}
private static Optional<Mono<Void>> zeroCopy(Resource resource, @Nullable ResourceRegion region,
ReactiveHttpOutputMessage message, Map<String, Object> hints) {
if (message instanceof ZeroCopyHttpOutputMessage && resource.isFile()) {
try {
File file = resource.getFile();
long pos = region != null ? region.getPosition() : 0;
long count = region != null ? region.getCount() : file.length();
if (logger.isDebugEnabled()) {
String formatted = region != null ? "region " + pos + "-" + (count) + " of " : "";
logger.debug(Hints.getLogPrefix(hints) + "Zero-copy " + formatted + "[" + resource + "]");
}
return Optional.of(((ZeroCopyHttpOutputMessage) message).writeWith(file, pos, count));
}
catch (IOException ex) {
// should not happen
}
}
return Optional.empty();
}
/**
* Convert each {@code HttpRange} into a {@code ResourceRegion}, selecting the
* appropriate segment of the given {@code Resource} using HTTP Range information.
* @param ranges the list of ranges
* @param resource the resource to select the regions from
* @return the list of regions for the given resource
* @throws IllegalArgumentException if the sum of all ranges exceeds the resource length
* @since 4.3
*/
public static List<ResourceRegion> toResourceRegions(List<HttpRange> ranges, Resource resource) {
if (CollectionUtils.isEmpty(ranges)) {
return Collections.emptyList();
}
List<ResourceRegion> regions = new ArrayList<>(ranges.size());
for (HttpRange range : ranges) {
regions.add(range.toResourceRegion(resource));
}
if (ranges.size() > 1) {
long length = getLengthFor(resource);
long total = 0;
for (ResourceRegion region : regions) {
total += region.getCount();
}
if (total >= length) {
throw new IllegalArgumentException("The sum of all ranges (" + total +
") should be less than the resource length (" + length + ")");
}
}
return regions;
}
@Test // SPR-15041
public void applicationOctetStreamDefaultContentType() throws Exception {
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
ClassPathResource body = Mockito.mock(ClassPathResource.class);
BDDMockito.given(body.getFilename()).willReturn("spring.dat");
BDDMockito.given(body.contentLength()).willReturn(12L);
BDDMockito.given(body.getInputStream()).willReturn(new ByteArrayInputStream("Spring Framework".getBytes()));
HttpRange range = HttpRange.createByteRange(0, 5);
ResourceRegion resourceRegion = range.toResourceRegion(body);
converter.write(Collections.singletonList(resourceRegion), null, outputMessage);
assertThat(outputMessage.getHeaders().getContentType(), is(MediaType.APPLICATION_OCTET_STREAM));
assertThat(outputMessage.getHeaders().getFirst(HttpHeaders.CONTENT_RANGE), is("bytes 0-5/12"));
assertThat(outputMessage.getBodyAsString(StandardCharsets.UTF_8), is("Spring"));
}
@Test
public void toResourceRegionsValidations() {
byte[] bytes = "12345".getBytes(StandardCharsets.UTF_8);
ByteArrayResource resource = new ByteArrayResource(bytes);
// 1. Below length
List<HttpRange> ranges = HttpRange.parseRanges("bytes=0-1,2-3");
List<ResourceRegion> regions = HttpRange.toResourceRegions(ranges, resource);
assertEquals(2, regions.size());
// 2. At length
ranges = HttpRange.parseRanges("bytes=0-1,2-4");
try {
HttpRange.toResourceRegions(ranges, resource);
fail();
}
catch (IllegalArgumentException ex) {
// Expected..
}
}
@Override
public boolean canWrite(@Nullable Type type, @Nullable Class<?> clazz, @Nullable MediaType mediaType) {
if (!(type instanceof ParameterizedType)) {
return (type instanceof Class && ResourceRegion.class.isAssignableFrom((Class<?>) type));
}
ParameterizedType parameterizedType = (ParameterizedType) type;
if (!(parameterizedType.getRawType() instanceof Class)) {
return false;
}
Class<?> rawType = (Class<?>) parameterizedType.getRawType();
if (!(Collection.class.isAssignableFrom(rawType))) {
return false;
}
if (parameterizedType.getActualTypeArguments().length != 1) {
return false;
}
Type typeArgument = parameterizedType.getActualTypeArguments()[0];
if (!(typeArgument instanceof Class)) {
return false;
}
Class<?> typeArgumentClass = (Class<?>) typeArgument;
return ResourceRegion.class.isAssignableFrom(typeArgumentClass);
}
@Override
@SuppressWarnings("unchecked")
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
if (object instanceof ResourceRegion) {
writeResourceRegion((ResourceRegion) object, outputMessage);
}
else {
Collection<ResourceRegion> regions = (Collection<ResourceRegion>) object;
if (regions.size() == 1) {
writeResourceRegion(regions.iterator().next(), outputMessage);
}
else {
writeResourceRegionCollection((Collection<ResourceRegion>) object, outputMessage);
}
}
}
protected void writeResourceRegion(ResourceRegion region, HttpOutputMessage outputMessage) throws IOException {
Assert.notNull(region, "ResourceRegion must not be null");
HttpHeaders responseHeaders = outputMessage.getHeaders();
long start = region.getPosition();
long end = start + region.getCount() - 1;
Long resourceLength = region.getResource().contentLength();
end = Math.min(end, resourceLength - 1);
long rangeLength = end - start + 1;
responseHeaders.add("Content-Range", "bytes " + start + '-' + end + '/' + resourceLength);
responseHeaders.setContentLength(rangeLength);
InputStream in = region.getResource().getInputStream();
try {
StreamUtils.copyRange(in, outputMessage.getBody(), start, end);
}
finally {
try {
in.close();
}
catch (IOException ex) {
// ignore
}
}
}
private static Optional<Mono<Void>> zeroCopy(Resource resource, @Nullable ResourceRegion region,
ReactiveHttpOutputMessage message, Map<String, Object> hints) {
if (message instanceof ZeroCopyHttpOutputMessage && resource.isFile()) {
try {
File file = resource.getFile();
long pos = region != null ? region.getPosition() : 0;
long count = region != null ? region.getCount() : file.length();
if (logger.isDebugEnabled()) {
String formatted = region != null ? "region " + pos + "-" + (count) + " of " : "";
logger.debug(Hints.getLogPrefix(hints) + "Zero-copy " + formatted + "[" + resource + "]");
}
return Optional.of(((ZeroCopyHttpOutputMessage) message).writeWith(file, pos, count));
}
catch (IOException ex) {
// should not happen
}
}
return Optional.empty();
}
/**
* Convert each {@code HttpRange} into a {@code ResourceRegion}, selecting the
* appropriate segment of the given {@code Resource} using HTTP Range information.
* @param ranges the list of ranges
* @param resource the resource to select the regions from
* @return the list of regions for the given resource
* @throws IllegalArgumentException if the sum of all ranges exceeds the
* resource length.
* @since 4.3
*/
public static List<ResourceRegion> toResourceRegions(List<HttpRange> ranges, Resource resource) {
if (CollectionUtils.isEmpty(ranges)) {
return Collections.emptyList();
}
List<ResourceRegion> regions = new ArrayList<>(ranges.size());
for (HttpRange range : ranges) {
regions.add(range.toResourceRegion(resource));
}
if (ranges.size() > 1) {
long length = getLengthFor(resource);
long total = regions.stream().map(ResourceRegion::getCount).reduce(0L, (count, sum) -> sum + count);
Assert.isTrue(total < length,
() -> "The sum of all ranges (" + total + ") " +
"should be less than the resource length (" + length + ")");
}
return regions;
}
@Test // SPR-15041
public void applicationOctetStreamDefaultContentType() throws Exception {
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
ClassPathResource body = Mockito.mock(ClassPathResource.class);
BDDMockito.given(body.getFilename()).willReturn("spring.dat");
BDDMockito.given(body.contentLength()).willReturn(12L);
BDDMockito.given(body.getInputStream()).willReturn(new ByteArrayInputStream("Spring Framework".getBytes()));
HttpRange range = HttpRange.createByteRange(0, 5);
ResourceRegion resourceRegion = range.toResourceRegion(body);
converter.write(Collections.singletonList(resourceRegion), null, outputMessage);
assertThat(outputMessage.getHeaders().getContentType(), is(MediaType.APPLICATION_OCTET_STREAM));
assertThat(outputMessage.getHeaders().getFirst(HttpHeaders.CONTENT_RANGE), is("bytes 0-5/12"));
assertThat(outputMessage.getBodyAsString(StandardCharsets.UTF_8), is("Spring"));
}
@Test
public void toResourceRegionsValidations() {
byte[] bytes = "12345".getBytes(StandardCharsets.UTF_8);
ByteArrayResource resource = new ByteArrayResource(bytes);
// 1. Below length
List<HttpRange> ranges = HttpRange.parseRanges("bytes=0-1,2-3");
List<ResourceRegion> regions = HttpRange.toResourceRegions(ranges, resource);
assertEquals(2, regions.size());
// 2. At length
ranges = HttpRange.parseRanges("bytes=0-1,2-4");
try {
HttpRange.toResourceRegions(ranges, resource);
fail();
}
catch (IllegalArgumentException ex) {
// Expected..
}
}
@Override
@SuppressWarnings("unchecked")
protected MediaType getDefaultContentType(Object object) {
if (jafPresent) {
if (object instanceof ResourceRegion) {
return ActivationMediaTypeFactory.getMediaType(((ResourceRegion) object).getResource());
}
else {
Collection<ResourceRegion> regions = (Collection<ResourceRegion>) object;
if (!regions.isEmpty()) {
return ActivationMediaTypeFactory.getMediaType(regions.iterator().next().getResource());
}
}
}
return MediaType.APPLICATION_OCTET_STREAM;
}
@Override
public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
if (!(type instanceof ParameterizedType)) {
return ResourceRegion.class.isAssignableFrom((Class<?>) type);
}
ParameterizedType parameterizedType = (ParameterizedType) type;
if (!(parameterizedType.getRawType() instanceof Class)) {
return false;
}
Class<?> rawType = (Class<?>) parameterizedType.getRawType();
if (!(Collection.class.isAssignableFrom(rawType))) {
return false;
}
if (parameterizedType.getActualTypeArguments().length != 1) {
return false;
}
Type typeArgument = parameterizedType.getActualTypeArguments()[0];
if (!(typeArgument instanceof Class)) {
return false;
}
Class<?> typeArgumentClass = (Class<?>) typeArgument;
return typeArgumentClass.isAssignableFrom(ResourceRegion.class);
}
@Override
@SuppressWarnings("unchecked")
protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
if (object instanceof ResourceRegion) {
writeResourceRegion((ResourceRegion) object, outputMessage);
}
else {
Collection<ResourceRegion> regions = (Collection<ResourceRegion>) object;
if (regions.size() == 1) {
writeResourceRegion(regions.iterator().next(), outputMessage);
}
else {
writeResourceRegionCollection((Collection<ResourceRegion>) object, outputMessage);
}
}
}
protected void writeResourceRegion(ResourceRegion region, HttpOutputMessage outputMessage) throws IOException {
Assert.notNull(region, "ResourceRegion must not be null");
HttpHeaders responseHeaders = outputMessage.getHeaders();
long start = region.getPosition();
long end = start + region.getCount() - 1;
Long resourceLength = region.getResource().contentLength();
end = Math.min(end, resourceLength - 1);
long rangeLength = end - start + 1;
responseHeaders.add("Content-Range", "bytes " + start + '-' + end + '/' + resourceLength);
responseHeaders.setContentLength(rangeLength);
InputStream in = region.getResource().getInputStream();
try {
StreamUtils.copyRange(in, outputMessage.getBody(), start, end);
}
finally {
try {
in.close();
}
catch (IOException ex) {
// ignore
}
}
}
/**
* Turn a {@code Resource} into a {@link ResourceRegion} using the range
* information contained in the current {@code HttpRange}.
* @param resource the {@code Resource} to select the region from
* @return the selected region of the given {@code Resource}
* @since 4.3
*/
public ResourceRegion toResourceRegion(Resource resource) {
// Don't try to determine contentLength on InputStreamResource - cannot be read afterwards...
// Note: custom InputStreamResource subclasses could provide a pre-calculated content length!
Assert.isTrue(resource.getClass() != InputStreamResource.class,
"Cannot convert an InputStreamResource to a ResourceRegion");
try {
long contentLength = resource.contentLength();
Assert.isTrue(contentLength > 0, "Resource content length should be > 0");
long start = getRangeStart(contentLength);
long end = getRangeEnd(contentLength);
return new ResourceRegion(resource, start, end - start + 1);
}
catch (IOException ex) {
throw new IllegalArgumentException("Failed to convert Resource to ResourceRegion", ex);
}
}
protected void writeResourceRegion(ResourceRegion region, HttpOutputMessage outputMessage) throws IOException {
Assert.notNull(region, "ResourceRegion must not be null");
HttpHeaders responseHeaders = outputMessage.getHeaders();
long start = region.getPosition();
long end = start + region.getCount() - 1;
Long resourceLength = region.getResource().contentLength();
end = Math.min(end, resourceLength - 1);
long rangeLength = end - start + 1;
responseHeaders.add("Content-Range", "bytes " + start + '-' + end + '/' + resourceLength);
responseHeaders.setContentLength(rangeLength);
InputStream in = null;
try {
Resource resource = region.getResource();
if (resource instanceof RangeAwareResource) {
in = ((RangeAwareResource) resource).getInputStream(start, end);
StreamUtils.copy(in, outputMessage.getBody());
} else {
in = resource.getInputStream();
StreamUtils.copyRange(in, outputMessage.getBody(), start, end);
}
} finally {
IOUtils.closeQuietly(in);
}
}
private Flux<DataBuffer> writeResourceRegion(
ResourceRegion region, DataBufferFactory bufferFactory, @Nullable Map<String, Object> hints) {
Resource resource = region.getResource();
long position = region.getPosition();
long count = region.getCount();
if (logger.isDebugEnabled() && !Hints.isLoggingSuppressed(hints)) {
logger.debug(Hints.getLogPrefix(hints) +
"Writing region " + position + "-" + (position + count) + " of [" + resource + "]");
}
Flux<DataBuffer> in = DataBufferUtils.read(resource, position, bufferFactory, this.bufferSize);
return DataBufferUtils.takeUntilByteCount(in, count);
}
private byte[] getContentRangeHeader(ResourceRegion region) {
long start = region.getPosition();
long end = start + region.getCount() - 1;
OptionalLong contentLength = contentLength(region.getResource());
if (contentLength.isPresent()) {
long length = contentLength.getAsLong();
return toAsciiBytes("Content-Range: bytes " + start + '-' + end + '/' + length + "\r\n\r\n");
}
else {
return toAsciiBytes("Content-Range: bytes " + start + '-' + end + "\r\n\r\n");
}
}
@Test
public void canEncode() {
ResolvableType resourceRegion = ResolvableType.forClass(ResourceRegion.class);
MimeType allMimeType = MimeType.valueOf("*/*");
assertFalse(this.encoder.canEncode(ResolvableType.forClass(Resource.class),
MimeTypeUtils.APPLICATION_OCTET_STREAM));
assertFalse(this.encoder.canEncode(ResolvableType.forClass(Resource.class), allMimeType));
assertTrue(this.encoder.canEncode(resourceRegion, MimeTypeUtils.APPLICATION_OCTET_STREAM));
assertTrue(this.encoder.canEncode(resourceRegion, allMimeType));
// SPR-15464
assertFalse(this.encoder.canEncode(ResolvableType.NONE, null));
}
@Test
public void shouldEncodeResourceRegionFileResource() throws Exception {
ResourceRegion region = new ResourceRegion(
new ClassPathResource("ResourceRegionEncoderTests.txt", getClass()), 0, 6);
Flux<DataBuffer> result = this.encoder.encode(Mono.just(region), this.bufferFactory,
ResolvableType.forClass(ResourceRegion.class),
MimeTypeUtils.APPLICATION_OCTET_STREAM,
Collections.emptyMap());
StepVerifier.create(result)
.consumeNextWith(stringConsumer("Spring"))
.expectComplete()
.verify();
}
@Test
public void shouldEncodeMultipleResourceRegionsFileResource() {
Resource resource = new ClassPathResource("ResourceRegionEncoderTests.txt", getClass());
Flux<ResourceRegion> regions = Flux.just(
new ResourceRegion(resource, 0, 6),
new ResourceRegion(resource, 7, 9),
new ResourceRegion(resource, 17, 4),
new ResourceRegion(resource, 22, 17)
);
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"))
.consumeNextWith(stringConsumer("\r\n--" + boundary + "\r\n"))
.consumeNextWith(stringConsumer("Content-Type: text/plain\r\n"))
.consumeNextWith(stringConsumer("Content-Range: bytes 7-15/39\r\n\r\n"))
.consumeNextWith(stringConsumer("Framework"))
.consumeNextWith(stringConsumer("\r\n--" + boundary + "\r\n"))
.consumeNextWith(stringConsumer("Content-Type: text/plain\r\n"))
.consumeNextWith(stringConsumer("Content-Range: bytes 17-20/39\r\n\r\n"))
.consumeNextWith(stringConsumer("test"))
.consumeNextWith(stringConsumer("\r\n--" + boundary + "\r\n"))
.consumeNextWith(stringConsumer("Content-Type: text/plain\r\n"))
.consumeNextWith(stringConsumer("Content-Range: bytes 22-38/39\r\n\r\n"))
.consumeNextWith(stringConsumer("resource content."))
.consumeNextWith(stringConsumer("\r\n--" + boundary + "--"))
.expectComplete()
.verify();
}
@Override
@SuppressWarnings("unchecked")
protected MediaType getDefaultContentType(Object object) {
Resource resource = null;
if (object instanceof ResourceRegion) {
resource = ((ResourceRegion) object).getResource();
}
else {
Collection<ResourceRegion> regions = (Collection<ResourceRegion>) object;
if (!regions.isEmpty()) {
resource = regions.iterator().next().getResource();
}
}
return MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM);
}
private Mono<Void> writeSingleRegion(ResourceRegion region, ReactiveHttpOutputMessage message,
Map<String, Object> hints) {
return zeroCopy(region.getResource(), region, message, hints)
.orElseGet(() -> {
Publisher<? extends ResourceRegion> input = Mono.just(region);
MediaType mediaType = message.getHeaders().getContentType();
return encodeAndWriteRegions(input, mediaType, message, hints);
});
}
private Mono<Void> encodeAndWriteRegions(Publisher<? extends ResourceRegion> publisher,
@Nullable MediaType mediaType, ReactiveHttpOutputMessage message, Map<String, Object> hints) {
Flux<DataBuffer> body = this.regionEncoder.encode(
publisher, message.bufferFactory(), REGION_TYPE, mediaType, hints);
return message.writeWith(body);
}