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); }
/** * 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"); long contentLength = getLengthFor(resource); long start = getRangeStart(contentLength); long end = getRangeEnd(contentLength); return new ResourceRegion(resource, start, end - start + 1); }
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(); }
@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 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(); }
@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 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 getAsciiBytes("Content-Range: bytes " + start + '-' + end + '/' + length + "\r\n\r\n"); } else { return getAsciiBytes("Content-Range: bytes " + start + '-' + end + "\r\n\r\n"); } }
long contentLength = lengthOf(resource); if (contentLength != -1) { long start = region.getPosition(); long end = start + region.getCount() - 1; end = Math.min(end, contentLength - 1); headers.add("Content-Range", "bytes " + start + '-' + end + '/' + contentLength);
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); }); }
@Test(expected = IllegalArgumentException.class) public void shouldThrowExceptionForNegativePosition() { new ResourceRegion(mock(Resource.class), -1, 1); }
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 } } }
long contentLength = lengthOf(resource); if (contentLength != -1) { long start = region.getPosition(); long end = start + region.getCount() - 1; end = Math.min(end, contentLength - 1); headers.add("Content-Range", "bytes " + start + '-' + end + '/' + contentLength);
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); }); }
@Test(expected = IllegalArgumentException.class) public void shouldThrowExceptionWithNullResource() { new ResourceRegion(null, 0, 1); }
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 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(); }
return Mono.from(inputStream) .flatMapMany(region -> { if (!region.getResource().isReadable()) { return Flux.error(new EncodingException("Resource " + region.getResource() + " is not readable")); if (!region.getResource().isReadable()) { return Flux.error(new EncodingException("Resource " + region.getResource() + " is not readable"));
@Test(expected = IllegalArgumentException.class) public void shouldThrowExceptionForNegativeCount() { new ResourceRegion(mock(Resource.class), 0, -1); }
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 getAsciiBytes("Content-Range: bytes " + start + '-' + end + '/' + length + "\r\n\r\n"); } else { return getAsciiBytes("Content-Range: bytes " + start + '-' + end + "\r\n\r\n"); } }
long contentLength = lengthOf(resource); if (contentLength != -1) { long start = region.getPosition(); long end = start + region.getCount() - 1; end = Math.min(end, contentLength - 1); headers.add("Content-Range", "bytes " + start + '-' + end + '/' + contentLength);