Issue #3840 - Static resource byte-range support performance
+ Reverting toFile().getInputStream() on PathResource + Adding RangeWriter concept for managing open resource across multiple range writes + RangeWriter implementation delegates to HttpContent behaviors Lookup is : - Direct Buffer - Indirect Buffer - ReadableByteChannel (as SeekableByteChannel) - InputStream + Adding unit tests for all RangeWriter implementation to ensure that they behave the same way everywhere. + Making ResourceService use new RangeWriter implementation + Existing DefaultServletRangeTest still works as-is Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
This commit is contained in:
parent
24b2ca4c32
commit
e5bce5f7cd
|
@ -20,7 +20,6 @@ package org.eclipse.jetty.server;
|
|||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
@ -47,9 +46,10 @@ import org.eclipse.jetty.http.PreEncodedHttpField;
|
|||
import org.eclipse.jetty.http.QuotedCSV;
|
||||
import org.eclipse.jetty.http.QuotedQualityCSV;
|
||||
import org.eclipse.jetty.io.WriterOutputStream;
|
||||
import org.eclipse.jetty.server.resource.HttpContentRangeWriter;
|
||||
import org.eclipse.jetty.server.resource.RangeWriter;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.MultiPartOutputStream;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
|
@ -779,9 +779,6 @@ public class ResourceService
|
|||
ctp = "multipart/byteranges; boundary=";
|
||||
response.setContentType(ctp + multi.getBoundary());
|
||||
|
||||
InputStream in = content.getResource().getInputStream();
|
||||
long pos = 0;
|
||||
|
||||
// calculate the content-length
|
||||
int length = 0;
|
||||
String[] header = new String[ranges.size()];
|
||||
|
@ -801,6 +798,8 @@ public class ResourceService
|
|||
length += 2 + 2 + multi.getBoundary().length() + 2 + 2;
|
||||
response.setContentLength(length);
|
||||
|
||||
try (RangeWriter rangeWriter = HttpContentRangeWriter.newRangeWriter(content))
|
||||
{
|
||||
i = 0;
|
||||
for (InclusiveByteRange ibr : ranges)
|
||||
{
|
||||
|
@ -808,32 +807,12 @@ public class ResourceService
|
|||
|
||||
long start = ibr.getFirst();
|
||||
long size = ibr.getSize();
|
||||
if (in != null)
|
||||
{
|
||||
// Handle non cached resource
|
||||
if (start < pos)
|
||||
{
|
||||
in.close();
|
||||
in = content.getResource().getInputStream();
|
||||
pos = 0;
|
||||
}
|
||||
if (pos < start)
|
||||
{
|
||||
in.skip(start - pos);
|
||||
pos = start;
|
||||
}
|
||||
|
||||
IO.copy(in, multi, size);
|
||||
pos += size;
|
||||
}
|
||||
else
|
||||
// Handle cached resource
|
||||
content.getResource().writeTo(multi, start, size);
|
||||
|
||||
rangeWriter.writeTo(multi, start, size);
|
||||
i++;
|
||||
}
|
||||
if (in != null)
|
||||
in.close();
|
||||
}
|
||||
|
||||
multi.close();
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.server.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
|
||||
/**
|
||||
* ByteBuffer based RangeWriter
|
||||
*/
|
||||
public class ByteBufferRangeWriter implements RangeWriter
|
||||
{
|
||||
private final ByteBuffer buffer;
|
||||
private boolean closed = false;
|
||||
|
||||
public ByteBufferRangeWriter(ByteBuffer buffer)
|
||||
{
|
||||
this.buffer = buffer.asReadOnlyBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
closed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream, long skipTo, long length) throws IOException
|
||||
{
|
||||
if (skipTo > Integer.MAX_VALUE)
|
||||
{
|
||||
throw new IllegalArgumentException("Unsupported skipTo " + skipTo + " > " + Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
if (length > Integer.MAX_VALUE)
|
||||
{
|
||||
throw new IllegalArgumentException("Unsupported length " + skipTo + " > " + Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
ByteBuffer src = buffer.slice();
|
||||
src.position((int)skipTo);
|
||||
src.limit(Math.addExact((int)skipTo, (int)length));
|
||||
BufferUtil.writeTo(src, outputStream);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.server.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jetty.http.HttpContent;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
/**
|
||||
* Range Writer selection for HttpContent
|
||||
*/
|
||||
public class HttpContentRangeWriter
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(HttpContentRangeWriter.class);
|
||||
|
||||
/**
|
||||
* Obtain a new RangeWriter for the supplied HttpContent.
|
||||
*
|
||||
* @param content the HttpContent to base RangeWriter on
|
||||
* @return the RangeWriter best suited for the supplied HttpContent
|
||||
*/
|
||||
public static RangeWriter newRangeWriter(HttpContent content)
|
||||
{
|
||||
Objects.requireNonNull(content, "HttpContent");
|
||||
|
||||
// Try direct buffer
|
||||
ByteBuffer buffer = content.getDirectBuffer();
|
||||
if (buffer == null)
|
||||
{
|
||||
buffer = content.getIndirectBuffer();
|
||||
}
|
||||
if (buffer != null)
|
||||
{
|
||||
return new ByteBufferRangeWriter(buffer);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ReadableByteChannel channel = content.getReadableByteChannel();
|
||||
if (channel != null)
|
||||
{
|
||||
if (channel instanceof SeekableByteChannel)
|
||||
{
|
||||
SeekableByteChannel seekableByteChannel = (SeekableByteChannel)channel;
|
||||
return new SeekableByteChannelRangeWriter(seekableByteChannel);
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Skipping non-SeekableByteChannel option " + channel + " from content " + content);
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Skipping ReadableByteChannel option", e);
|
||||
}
|
||||
|
||||
return new InputStreamRangeWriter(() -> content.getInputStream());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.server.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.eclipse.jetty.util.IO;
|
||||
|
||||
/**
|
||||
* Default Range Writer for InputStream
|
||||
*/
|
||||
public class InputStreamRangeWriter implements RangeWriter
|
||||
{
|
||||
public interface InputStreamSupplier
|
||||
{
|
||||
InputStream newInputStream() throws IOException;
|
||||
}
|
||||
|
||||
private final InputStreamSupplier inputStreamSupplier;
|
||||
private boolean closed = false;
|
||||
private InputStream inputStream;
|
||||
private long pos;
|
||||
|
||||
/**
|
||||
* Create InputStremRangeWriter
|
||||
*
|
||||
* @param inputStreamSupplier Supplier of the InputStream. If the stream needs to be regenerated, such as the next
|
||||
* requested range being before the current position, then the current InputStream is closed and a new one obtained
|
||||
* from this supplier.
|
||||
*/
|
||||
public InputStreamRangeWriter(InputStreamSupplier inputStreamSupplier)
|
||||
{
|
||||
this.inputStreamSupplier = inputStreamSupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
closed = true;
|
||||
if (inputStream != null)
|
||||
{
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream, long skipTo, long length) throws IOException
|
||||
{
|
||||
if (closed)
|
||||
{
|
||||
throw new IOException("RangeWriter is closed");
|
||||
}
|
||||
|
||||
if (inputStream == null)
|
||||
{
|
||||
inputStream = inputStreamSupplier.newInputStream();
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
if (skipTo < pos)
|
||||
{
|
||||
inputStream.close();
|
||||
inputStream = inputStreamSupplier.newInputStream();
|
||||
pos = 0;
|
||||
}
|
||||
if (pos < skipTo)
|
||||
{
|
||||
inputStream.skip(skipTo - pos);
|
||||
pos = skipTo;
|
||||
}
|
||||
|
||||
IO.copy(inputStream, outputStream, length);
|
||||
pos += length;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.server.resource;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Interface for writing sections (ranges) of a single resource (SeekableByteChannel, Resource, etc) to an outputStream.
|
||||
*/
|
||||
public interface RangeWriter extends Closeable
|
||||
{
|
||||
/**
|
||||
* Write the specific range (start, size) to the outputStream.
|
||||
*
|
||||
* @param outputStream the stream to write to
|
||||
* @param skipTo the offset / skip-to / seek-to / position in the resource to start the write from
|
||||
* @param length the size of the section to write
|
||||
*/
|
||||
void writeTo(OutputStream outputStream, long skipTo, long length) throws IOException;
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.server.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
|
||||
public class SeekableByteChannelRangeWriter implements RangeWriter
|
||||
{
|
||||
private final SeekableByteChannel channel;
|
||||
private final int bufSize;
|
||||
private final ByteBuffer buffer;
|
||||
|
||||
public SeekableByteChannelRangeWriter(SeekableByteChannel seekableByteChannel)
|
||||
{
|
||||
this.channel = seekableByteChannel;
|
||||
this.bufSize = IO.bufferSize;
|
||||
this.buffer = BufferUtil.allocate(this.bufSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
this.channel.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream, long skipTo, long length) throws IOException
|
||||
{
|
||||
this.channel.position(skipTo);
|
||||
|
||||
// copy from channel to output stream
|
||||
long readTotal = 0;
|
||||
while (readTotal < length)
|
||||
{
|
||||
BufferUtil.flipToFill(buffer);
|
||||
int size = (int)Math.min(bufSize, length - readTotal);
|
||||
buffer.limit(size);
|
||||
int readLen = channel.read(buffer);
|
||||
BufferUtil.flipToFlush(buffer, 0);
|
||||
BufferUtil.writeTo(buffer, outputStream);
|
||||
readTotal += readLen;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.server.resource;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jetty.toolchain.test.FS;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.resource.PathResource;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class RangeWriterTest
|
||||
{
|
||||
public static final String DATA = "01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWZYZ!@#$%^&*()_+/.,[]";
|
||||
|
||||
public static Path initDataFile() throws IOException
|
||||
{
|
||||
Path testDir = MavenTestingUtils.getTargetTestingPath(RangeWriterTest.class.getSimpleName());
|
||||
FS.ensureEmpty(testDir);
|
||||
|
||||
Path dataFile = testDir.resolve("data.dat");
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(dataFile, UTF_8, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))
|
||||
{
|
||||
writer.write(DATA);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
return dataFile;
|
||||
}
|
||||
|
||||
public static Stream<Arguments> impls() throws IOException
|
||||
{
|
||||
Resource resource = new PathResource(initDataFile());
|
||||
|
||||
return Stream.of(
|
||||
Arguments.of(new ByteBufferRangeWriter(BufferUtil.toBuffer(resource, true))),
|
||||
Arguments.of(new ByteBufferRangeWriter(BufferUtil.toBuffer(resource, false))),
|
||||
Arguments.of(new SeekableByteChannelRangeWriter((SeekableByteChannel)resource.getReadableByteChannel())),
|
||||
Arguments.of(new InputStreamRangeWriter(() -> resource.getInputStream()))
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("impls")
|
||||
public void testSimpleRange(RangeWriter rangeWriter) throws IOException
|
||||
{
|
||||
ByteArrayOutputStream outputStream;
|
||||
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
rangeWriter.writeTo(outputStream, 10, 50);
|
||||
assertThat("Range: 10 (len=50)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(10, 60)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("impls")
|
||||
public void testSameRange_MultipleTimes(RangeWriter rangeWriter) throws IOException
|
||||
{
|
||||
ByteArrayOutputStream outputStream;
|
||||
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
rangeWriter.writeTo(outputStream, 10, 50);
|
||||
assertThat("Range(a): 10 (len=50)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(10, 60)));
|
||||
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
rangeWriter.writeTo(outputStream, 10, 50);
|
||||
assertThat("Range(b): 10 (len=50)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(10, 60)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("impls")
|
||||
public void testMultipleRanges_Ordered(RangeWriter rangeWriter) throws IOException
|
||||
{
|
||||
ByteArrayOutputStream outputStream;
|
||||
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
rangeWriter.writeTo(outputStream, 10, 20);
|
||||
assertThat("Range(a): 10 (len=20)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(10, 10 + 20)));
|
||||
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
rangeWriter.writeTo(outputStream, 35, 10);
|
||||
assertThat("Range(b): 35 (len=10)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(35, 35 + 10)));
|
||||
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
rangeWriter.writeTo(outputStream, 55, 10);
|
||||
assertThat("Range(b): 55 (len=10)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(55, 55 + 10)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("impls")
|
||||
public void testMultipleRanges_Overlapping(RangeWriter rangeWriter) throws IOException
|
||||
{
|
||||
ByteArrayOutputStream outputStream;
|
||||
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
rangeWriter.writeTo(outputStream, 10, 20);
|
||||
assertThat("Range(a): 10 (len=20)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(10, 10 + 20)));
|
||||
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
rangeWriter.writeTo(outputStream, 15, 20);
|
||||
assertThat("Range(b): 15 (len=20)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(15, 15 + 20)));
|
||||
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
rangeWriter.writeTo(outputStream, 20, 20);
|
||||
assertThat("Range(b): 20 (len=20)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(20, 20 + 20)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("impls")
|
||||
public void testMultipleRanges_ReverseOrder(RangeWriter rangeWriter) throws IOException
|
||||
{
|
||||
ByteArrayOutputStream outputStream;
|
||||
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
rangeWriter.writeTo(outputStream, 55, 10);
|
||||
assertThat("Range(b): 55 (len=10)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(55, 55 + 10)));
|
||||
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
rangeWriter.writeTo(outputStream, 35, 10);
|
||||
assertThat("Range(b): 35 (len=10)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(35, 35 + 10)));
|
||||
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
rangeWriter.writeTo(outputStream, 10, 20);
|
||||
assertThat("Range(a): 10 (len=20)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(10, 10 + 20)));
|
||||
}
|
||||
}
|
|
@ -19,14 +19,12 @@
|
|||
package org.eclipse.jetty.util.resource;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.file.DirectoryIteratorException;
|
||||
import java.nio.file.DirectoryStream;
|
||||
|
@ -379,9 +377,7 @@ public class PathResource extends Resource
|
|||
@Override
|
||||
public InputStream getInputStream() throws IOException
|
||||
{
|
||||
// Use a FileInputStream rather than Files.newInputStream(path)
|
||||
// since it produces a stream with a fast skip implementation
|
||||
return new FileInputStream(getFile());
|
||||
return Files.newInputStream(path, StandardOpenOption.READ, StandardOpenOption.SPARSE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -393,7 +389,7 @@ public class PathResource extends Resource
|
|||
@Override
|
||||
public ReadableByteChannel getReadableByteChannel() throws IOException
|
||||
{
|
||||
return FileChannel.open(path, StandardOpenOption.READ);
|
||||
return Files.newByteChannel(path, StandardOpenOption.READ, StandardOpenOption.SPARSE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
Loading…
Reference in New Issue