Merge pull request #4558 from eclipse/jetty-10.0.x-3924-deprecate-resource-writeto

Issue #3924 - Remove Resource.writeTo(OutputStream, long, long)
This commit is contained in:
Joakim Erdfelt 2020-02-11 14:01:43 -06:00 committed by GitHub
commit a1c20b6cfa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 49 additions and 143 deletions

View File

@ -20,6 +20,7 @@ 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.util.Collection;
@ -46,9 +47,11 @@ 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.InputStreamRangeWriter;
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;
@ -669,18 +672,14 @@ public class ResourceService
if (include)
{
// write without headers
content.getResource().writeTo(out, 0, content_length);
writeContent(content, out, 0, content_length);
}
// else if we can't do a bypass write because of wrapping
else if (written || !(out instanceof HttpOutput))
{
// write normally
putHeaders(response, content, written ? -1 : 0);
ByteBuffer buffer = content.getIndirectBuffer();
if (buffer != null)
BufferUtil.writeTo(buffer, out);
else
content.getResource().writeTo(out, 0, content_length);
writeContent(content, out, 0, content_length);
}
// else do a bypass write
else
@ -753,7 +752,7 @@ public class ResourceService
response.addDateHeader(HttpHeader.DATE.asString(), System.currentTimeMillis());
response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
singleSatisfiableRange.toHeaderRangeString(content_length));
content.getResource().writeTo(out, singleSatisfiableRange.getFirst(), singleLength);
writeContent(content, out, singleSatisfiableRange.getFirst(), singleLength);
return true;
}
@ -815,6 +814,33 @@ public class ResourceService
return true;
}
private static void writeContent(HttpContent content, OutputStream out, long start, long contentLength) throws IOException
{
// Is the write for the whole content?
if (start == 0 && content.getResource().length() == contentLength)
{
// attempt efficient ByteBuffer based write for whole content
ByteBuffer buffer = content.getIndirectBuffer();
if (buffer != null)
{
BufferUtil.writeTo(buffer, out);
return;
}
try (InputStream input = content.getResource().getInputStream())
{
IO.copy(input, out);
return;
}
}
// Use a ranged writer
try (InputStreamRangeWriter rangeWriter = new InputStreamRangeWriter(() -> content.getInputStream()))
{
rangeWriter.writeTo(out, start, contentLength);
}
}
protected void putHeaders(HttpServletResponse response, HttpContent content, long contentLength)
{
if (response instanceof Response)

View File

@ -43,7 +43,7 @@ public class InputStreamRangeWriter implements RangeWriter
private long pos;
/**
* Create InputStremRangeWriter
* Create InputStreamRangeWriter
*
* @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

View File

@ -594,94 +594,6 @@ public class PathResource extends Resource
}
}
/**
* @param outputStream the output stream to write to
* @param start First byte to write
* @param count Bytes to write or -1 for all of them.
* @throws IOException if unable to copy the Resource to the output
*/
@Override
public void writeTo(OutputStream outputStream, long start, long count)
throws IOException
{
long length = count;
if (count < 0)
{
length = Files.size(path) - start;
}
try (SeekableByteChannel channel = Files.newByteChannel(path, StandardOpenOption.READ))
{
ByteBuffer buffer = BufferUtil.allocate(IO.bufferSize);
skipTo(channel, buffer, start);
// copy from channel to output stream
long readTotal = 0;
while (readTotal < length)
{
BufferUtil.clearToFill(buffer);
int size = (int)Math.min(IO.bufferSize, length - readTotal);
buffer.limit(size);
int readLen = channel.read(buffer);
BufferUtil.flipToFlush(buffer, 0);
BufferUtil.writeTo(buffer, outputStream);
readTotal += readLen;
}
}
}
private void skipTo(SeekableByteChannel channel, ByteBuffer buffer, long skipTo) throws IOException
{
try
{
if (channel.position() != skipTo)
{
channel.position(skipTo);
}
}
catch (UnsupportedOperationException e)
{
final int NO_PROGRESS_LIMIT = 3;
if (skipTo > 0)
{
long pos = 0;
long readLen;
int noProgressLoopLimit = NO_PROGRESS_LIMIT;
// loop till we reach desired point, break out on lack of progress.
while (noProgressLoopLimit > 0 && pos < skipTo)
{
BufferUtil.clearToFill(buffer);
int len = (int)Math.min(IO.bufferSize, (skipTo - pos));
buffer.limit(len);
readLen = channel.read(buffer);
if (readLen == 0)
{
noProgressLoopLimit--;
}
else if (readLen > 0)
{
pos += readLen;
noProgressLoopLimit = NO_PROGRESS_LIMIT;
}
else
{
// negative values means the stream was closed or reached EOF
// either way, we've hit a state where we can no longer
// fulfill the requested range write.
throw new IOException("EOF reached before SeekableByteChannel skip destination");
}
}
if (noProgressLoopLimit <= 0)
{
throw new IOException("No progress made to reach SeekableByteChannel skip position " + skipTo);
}
}
}
}
@Override
public String toString()
{

View File

@ -28,7 +28,9 @@ import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Base64;
@ -818,25 +820,6 @@ public abstract class Resource implements ResourceFactory, Closeable
return StringUtil.sanitizeXmlString(raw);
}
/**
* @param out the output stream to write to
* @param start First byte to write
* @param count Bytes to write or -1 for all of them.
* @throws IOException if unable to copy the Resource to the output
*/
public void writeTo(OutputStream out, long start, long count)
throws IOException
{
try (InputStream in = getInputStream())
{
in.skip(start);
if (count < 0)
IO.copy(in, out);
else
IO.copy(in, out, count);
}
}
/**
* Copy the Resource to the new destination file.
* <p>
@ -851,9 +834,20 @@ public abstract class Resource implements ResourceFactory, Closeable
if (destination.exists())
throw new IllegalArgumentException(destination + " exists");
try (OutputStream out = new FileOutputStream(destination))
// attempt simple file copy
File src = getFile();
if (src != null)
{
writeTo(out, 0, -1);
Files.copy(src.toPath(), destination.toPath(),
StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
return;
}
// use old school stream based copy
try (InputStream in = getInputStream();
OutputStream out = new FileOutputStream(destination))
{
IO.copy(in, out);
}
}

View File

@ -111,32 +111,6 @@ public class PathResourceTest
}
}
@Test
public void testNonDefaultFileSystemWriteTo() throws URISyntaxException, IOException
{
Path exampleJar = MavenTestingUtils.getTestResourcePathFile("example.jar");
URI uri = new URI("jar", exampleJar.toUri().toASCIIString(), null);
Map<String, Object> env = new HashMap<>();
env.put("multi-release", "runtime");
try (FileSystem zipfs = FileSystems.newFileSystem(uri, env))
{
Path manifestPath = zipfs.getPath("/META-INF/MANIFEST.MF");
assertThat(manifestPath, is(not(nullValue())));
PathResource resource = new PathResource(manifestPath);
try (ByteArrayOutputStream out = new ByteArrayOutputStream())
{
resource.writeTo(out, 2, 10);
String actual = new String(out.toByteArray(), UTF_8);
String expected = "nifest-Ver";
assertThat("writeTo(out, 2, 10)", actual, is(expected));
}
}
}
@Test
public void testDefaultFileSystemGetFile() throws Exception
{