Cleanup Resource IO (#11364)

Refactorings to rationalize and simplify how we do IO with resources internally by introducing the IOResources helper.

Signed-off-by: Ludovic Orban <lorban@bitronix.be>
This commit is contained in:
Ludovic Orban 2024-03-28 11:08:44 +01:00 committed by GitHub
parent 48f6ab7289
commit 89c41b2550
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 973 additions and 310 deletions

View File

@ -334,7 +334,7 @@ public class HTTP2ServerDocs
// Send the favicon "response".
MetaData.Response pushedResponse = new MetaData.Response(HttpStatus.OK_200, null, HttpVersion.HTTP_2, HttpFields.EMPTY);
return pushedStream.headers(new HeadersFrame(pushedStream.getId(), pushedResponse, null, false))
.thenCompose(pushed -> pushed.data(new DataFrame(pushed.getId(), faviconBuffer, true)));
.thenCompose(pushed -> pushed.data(new DataFrame(pushed.getId(), faviconBuffer.slice(), true)));
});
}
// Return a Stream.Listener to handle the request events.

View File

@ -45,6 +45,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -247,8 +248,8 @@ public class NetworkTrafficListenerTest
assertTrue(serverIncomingLatch.await(1, TimeUnit.SECONDS));
assertTrue(serverOutgoingLatch.await(1, TimeUnit.SECONDS));
assertTrue(clientIncomingLatch.await(1, TimeUnit.SECONDS));
assertEquals(clientOutgoing.get(), serverIncoming.get());
assertEquals(serverOutgoing.get(), clientIncoming.get());
await().atMost(5, TimeUnit.SECONDS).until(() -> clientOutgoing.get().equals(serverIncoming.get()));
await().atMost(5, TimeUnit.SECONDS).until(() -> serverOutgoing.get().equals(clientIncoming.get()));
}
@Test

View File

@ -32,6 +32,7 @@ import java.util.function.Supplier;
import java.util.stream.Stream;
import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.io.IOResources;
import org.eclipse.jetty.server.Deployable;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
@ -149,7 +150,7 @@ public class ContextProvider extends ScanningAppProvider
public void loadProperties(Resource resource) throws IOException
{
Properties props = new Properties();
try (InputStream inputStream = resource.newInputStream())
try (InputStream inputStream = IOResources.asInputStream(resource))
{
props.load(inputStream);
props.forEach((key, value) -> _properties.put((String)key, (String)value));

View File

@ -23,8 +23,9 @@ import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.io.IOResources;
import org.eclipse.jetty.io.content.ContentSourceCompletableFuture;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.thread.AutoLock;
@ -214,36 +215,37 @@ public class MultiPartByteRanges
{
private final Resource resource;
private final ByteRange byteRange;
private final ByteBufferPool bufferPool;
public Part(String contentType, Resource resource, ByteRange byteRange, long contentLength)
{
this(HttpFields.build().put(HttpHeader.CONTENT_TYPE, contentType)
.put(HttpHeader.CONTENT_RANGE, byteRange.toHeaderValue(contentLength)), resource, byteRange);
.put(HttpHeader.CONTENT_RANGE, byteRange.toHeaderValue(contentLength)), resource, byteRange, null);
}
public Part(String contentType, Resource resource, ByteRange byteRange, long contentLength, ByteBufferPool bufferPool)
{
this(HttpFields.build().put(HttpHeader.CONTENT_TYPE, contentType)
.put(HttpHeader.CONTENT_RANGE, byteRange.toHeaderValue(contentLength)), resource, byteRange, bufferPool);
}
public Part(HttpFields headers, Resource resource, ByteRange byteRange)
{
this(headers, resource, byteRange, null);
}
public Part(HttpFields headers, Resource resource, ByteRange byteRange, ByteBufferPool bufferPool)
{
super(null, null, headers);
this.resource = resource;
this.byteRange = byteRange;
this.bufferPool = bufferPool == null ? new ByteBufferPool.NonPooling() : bufferPool;
}
@Override
public Content.Source newContentSource()
{
// Try using the resource's path if possible, as the nio API is async and helps to avoid buffer copies.
Path path = resource.getPath();
if (path != null)
return new PathContentSource(path, byteRange);
try
{
return new InputStreamContentSource(resource.newInputStream(), byteRange);
}
catch (IOException e)
{
throw new RuntimeIOException(e);
}
return IOResources.asContentSource(resource, bufferPool, 0, false, byteRange.first(), byteRange.getLength());
}
}

View File

@ -15,7 +15,6 @@ package org.eclipse.jetty.http.content;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.time.Instant;
import java.util.Set;
import java.util.SortedSet;
@ -31,9 +30,9 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.IOResources;
import org.eclipse.jetty.io.Retainable;
import org.eclipse.jetty.io.RetainableByteBuffer;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.resource.Resource;
@ -333,31 +332,21 @@ public class CachingHttpContentFactory implements HttpContent.Factory
ByteBuffer byteBuffer = httpContent.getByteBuffer();
if (byteBuffer == null)
{
buffer = _bufferPool.acquire((int)_contentLengthValue, _useDirectByteBuffers);
try
{
if (_contentLengthValue <= _maxCachedFileSize)
{
try (ReadableByteChannel readableByteChannel = httpContent.getResource().newReadableByteChannel())
{
byteBuffer = buffer.getByteBuffer();
int read = BufferUtil.readFrom(readableByteChannel, byteBuffer);
if (read != _contentLengthValue)
{
buffer.release();
buffer = IOResources.toRetainableByteBuffer(httpContent.getResource(), _bufferPool, _useDirectByteBuffers);
else
buffer = null;
isValid = false;
}
}
}
}
catch (Throwable t)
{
if (buffer != null)
buffer.release();
buffer = null;
isValid = false;
LOG.warn("Failed to read Resource", t);
if (LOG.isDebugEnabled())
LOG.warn("Failed to read Resource: {}", httpContent.getResource(), t);
else
LOG.warn("Failed to read Resource: {} - {}", httpContent.getResource(), t.toString());
}
}
else

View File

@ -0,0 +1,512 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.io;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import org.eclipse.jetty.io.content.ByteBufferContentSource;
import org.eclipse.jetty.io.content.InputStreamContentSource;
import org.eclipse.jetty.io.content.PathContentSource;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.IteratingNestedCallback;
import org.eclipse.jetty.util.resource.MemoryResource;
import org.eclipse.jetty.util.resource.Resource;
/**
* Common IO operations for {@link Resource} content.
*/
public class IOResources
{
/**
* <p>Reads the contents of a Resource into a RetainableByteBuffer.</p>
* <p>The resource must not be a directory, must exists and there must be
* a way to access its contents.</p>
* <p>Multiple optimized methods are used to access the resource's contents but if they all fail,
* {@link Resource#newInputStream()} is used as a fallback.</p>
*
* @param resource the resource to be read.
* @param bufferPool the {@link ByteBufferPool} to get buffers from. null means allocate new buffers as needed.
* @param direct the directness of the buffers.
* @return a {@link RetainableByteBuffer} containing the resource's contents.
* @throws IllegalArgumentException if the resource is a directory or does not exist or there is no way to access its contents.
*/
public static RetainableByteBuffer toRetainableByteBuffer(Resource resource, ByteBufferPool bufferPool, boolean direct) throws IllegalArgumentException
{
if (resource.isDirectory() || !resource.exists())
throw new IllegalArgumentException("Resource must exist and cannot be a directory: " + resource);
// Optimize for MemoryResource.
if (resource instanceof MemoryResource memoryResource)
return RetainableByteBuffer.wrap(ByteBuffer.wrap(memoryResource.getBytes()));
long longLength = resource.length();
if (longLength > Integer.MAX_VALUE)
throw new IllegalArgumentException("Resource length exceeds 2 GiB: " + resource);
int length = (int)longLength;
bufferPool = bufferPool == null ? new ByteBufferPool.NonPooling() : bufferPool;
// Optimize for PathResource.
Path path = resource.getPath();
if (path != null)
{
RetainableByteBuffer retainableByteBuffer = bufferPool.acquire(length, direct);
try (SeekableByteChannel seekableByteChannel = Files.newByteChannel(path))
{
long totalRead = 0L;
ByteBuffer byteBuffer = retainableByteBuffer.getByteBuffer();
int pos = BufferUtil.flipToFill(byteBuffer);
while (totalRead < length)
{
int read = seekableByteChannel.read(byteBuffer);
if (read == -1)
break;
totalRead += read;
}
BufferUtil.flipToFlush(byteBuffer, pos);
return retainableByteBuffer;
}
catch (IOException e)
{
retainableByteBuffer.release();
throw new RuntimeIOException(e);
}
}
// Fallback to InputStream.
try (InputStream inputStream = resource.newInputStream())
{
if (inputStream == null)
throw new IllegalArgumentException("Resource does not support InputStream: " + resource);
ByteBufferAggregator aggregator = new ByteBufferAggregator(bufferPool, direct, length > -1 ? length : 4096, length > -1 ? length : Integer.MAX_VALUE);
byte[] byteArray = new byte[4096];
while (true)
{
int read = inputStream.read(byteArray);
if (read == -1)
break;
aggregator.aggregate(ByteBuffer.wrap(byteArray, 0, read));
}
return aggregator.takeRetainableByteBuffer();
}
catch (IOException e)
{
throw new RuntimeIOException(e);
}
}
/**
* <p>Gets a {@link Content.Source} with the contents of a resource.</p>
* <p>The resource must not be a directory, must exists and there must be
* a way to access its contents.</p>
* <p>Multiple optimized methods are used to access the resource's contents but if they all fail,
* {@link Resource#newInputStream()} is used as a fallback.</p>
*
* @param resource the resource from which to get a {@link Content.Source}.
* @param bufferPool the {@link ByteBufferPool} to get buffers from. null means allocate new buffers as needed.
* @param bufferSize the size of the buffer to be used for the copy. Any value &lt; 1 means use a default value.
* @param direct the directness of the buffers, this parameter is ignored if {@code bufferSize} is &lt; 1.
* @return the {@link Content.Source}.
* @throws IllegalArgumentException if the resource is a directory or does not exist or there is no way to access its contents.
*/
public static Content.Source asContentSource(Resource resource, ByteBufferPool bufferPool, int bufferSize, boolean direct) throws IllegalArgumentException
{
if (resource.isDirectory() || !resource.exists())
throw new IllegalArgumentException("Resource must exist and cannot be a directory: " + resource);
// Try to find an optimized content source.
Path path = resource.getPath();
if (path != null)
{
PathContentSource pathContentSource = new PathContentSource(path, bufferPool);
if (bufferSize > 0)
{
pathContentSource.setBufferSize(bufferSize);
pathContentSource.setUseDirectByteBuffers(direct);
}
return pathContentSource;
}
if (resource instanceof MemoryResource memoryResource)
{
byte[] bytes = memoryResource.getBytes();
return new ByteBufferContentSource(ByteBuffer.wrap(bytes));
}
// Fallback to wrapping InputStream.
try
{
return new InputStreamContentSource(resource.newInputStream());
}
catch (IOException e)
{
throw new RuntimeIOException(e);
}
}
/**
* <p>Gets a {@link Content.Source} with a range of the contents of a resource.</p>
* <p>The resource must not be a directory, must exists and there must be
* a way to access its contents.</p>
* <p>Multiple optimized methods are used to access the resource's contents but if they all fail,
* {@link Resource#newInputStream()} is used as a fallback.</p>
*
* @param resource the resource from which to get a {@link Content.Source}.
* @param bufferPool the {@link ByteBufferPool} to get buffers from. null means allocate new buffers as needed.
* @param bufferSize the size of the buffer to be used for the copy. Any value &lt; 1 means use a default value.
* @param direct the directness of the buffers, this parameter is ignored if {@code bufferSize} is &lt; 1.
* @param first the first byte from which to read from.
* @param length the length of the content to read.
* @return the {@link Content.Source}.
* @throws IllegalArgumentException if the resource is a directory or does not exist or there is no way to access its contents.
*/
public static Content.Source asContentSource(Resource resource, ByteBufferPool bufferPool, int bufferSize, boolean direct, long first, long length) throws IllegalArgumentException
{
if (resource.isDirectory() || !resource.exists())
throw new IllegalArgumentException("Resource must exist and cannot be a directory: " + resource);
// Try using the resource's path if possible, as the nio API is async and helps to avoid buffer copies.
Path path = resource.getPath();
if (path != null)
{
RangedPathContentSource contentSource = new RangedPathContentSource(path, bufferPool, first, length);
if (bufferSize > 0)
{
contentSource.setBufferSize(bufferSize);
contentSource.setUseDirectByteBuffers(direct);
}
return contentSource;
}
// Try an optimization for MemoryResource.
if (resource instanceof MemoryResource memoryResource)
return new ByteBufferContentSource(ByteBuffer.wrap(memoryResource.getBytes()));
// Fallback to InputStream.
try
{
InputStream inputStream = resource.newInputStream();
if (inputStream == null)
throw new IllegalArgumentException("Resource does not support InputStream: " + resource);
RangedInputStreamContentSource contentSource = new RangedInputStreamContentSource(inputStream, bufferPool, first, length);
if (bufferSize > 0)
{
contentSource.setBufferSize(bufferSize);
contentSource.setUseDirectByteBuffers(direct);
}
return contentSource;
}
catch (IOException e)
{
throw new RuntimeIOException(e);
}
}
/**
* <p>Gets an {@link InputStream} with the contents of a resource.</p>
* <p>The resource must not be a directory, must exist and must return non-null to {@link Resource#newInputStream()}.</p>
*
* @param resource the resource from which to get an {@link InputStream}.
* @return the {@link InputStream}.
* @throws IllegalArgumentException if the resource is a directory or does not exist or {@link Resource#newInputStream()} returns null.
*/
public static InputStream asInputStream(Resource resource) throws IllegalArgumentException
{
if (resource.isDirectory() || !resource.exists())
throw new IllegalArgumentException("Resource must exist and cannot be a directory: " + resource);
try
{
InputStream inputStream = resource.newInputStream();
if (inputStream == null)
throw new IllegalArgumentException("Resource does not support InputStream: " + resource);
return inputStream;
}
catch (IOException e)
{
throw new RuntimeIOException(e);
}
}
/**
* <p>Performs an asynchronous copy of the contents of a resource to a sink, using the given buffer pool and buffer characteristics.</p>
* <p>The resource must not be a directory, must exist and there must be a way to access its contents.</p>
* <p>Multiple optimized methods are used to access the resource's contents but if they all fail,
* {@link #asContentSource(Resource, ByteBufferPool, int, boolean)} is used as a fallback to perform the
* {@link Content#copy(Content.Source, Content.Sink, Callback) copy}.</p>
*
* @param resource the resource to copy from.
* @param sink the sink to copy to.
* @param bufferPool the {@link ByteBufferPool} to get buffers from. null means allocate new buffers as needed.
* @param bufferSize the size of the buffer to be used for the copy. Any value &lt; 1 means use a default value.
* @param direct the directness of the buffers, this parameter is ignored if {@code bufferSize} is &lt; 1.
* @param callback the callback to notify when the copy is done.
* @throws IllegalArgumentException if the resource is a directory or does not exist or there is no way to access its contents.
*/
public static void copy(Resource resource, Content.Sink sink, ByteBufferPool bufferPool, int bufferSize, boolean direct, Callback callback) throws IllegalArgumentException
{
if (resource.isDirectory() || !resource.exists())
throw new IllegalArgumentException("Resource must exist and cannot be a directory: " + resource);
// Save a Content.Source allocation for resources with a Path.
Path path = resource.getPath();
if (path != null)
{
try
{
new PathToSinkCopier(path, sink, bufferPool, bufferSize, direct, callback).iterate();
}
catch (Throwable x)
{
callback.failed(x);
}
return;
}
// Directly write the byte array if the resource is a MemoryResource.
if (resource instanceof MemoryResource memoryResource)
{
byte[] bytes = memoryResource.getBytes();
sink.write(true, ByteBuffer.wrap(bytes), callback);
return;
}
// Fallback to Content.Source.
Content.Source source = asContentSource(resource, bufferPool, bufferSize, direct);
Content.copy(source, sink, callback);
}
/**
* <p>Performs an asynchronous copy of a subset of the contents of a resource to a sink, using the given buffer pool and buffer characteristics.</p>
* <p>The resource must not be a directory, must exist and there must be a way to access its contents.</p>
* <p>Multiple optimized methods are used to access the resource's contents but if they all fail,
* {@link #asContentSource(Resource, ByteBufferPool, int, boolean, long, long)} is used as a fallback to perform the
* {@link Content#copy(Content.Source, Content.Sink, Callback) copy}.</p>
*
* @param resource the resource to copy from.
* @param sink the sink to copy to.
* @param bufferPool the {@link ByteBufferPool} to get buffers from. null means allocate new buffers as needed.
* @param bufferSize the size of the buffer to be used for the copy. Any value &lt; 1 means use a default value.
* @param direct the directness of the buffers, this parameter is ignored if {@code bufferSize} is &lt; 1.
* @param first the first byte of the resource to start from.
* @param length the length of the resource's contents to copy.
* @param callback the callback to notify when the copy is done.
* @throws IllegalArgumentException if the resource is a directory or does not exist or there is no way to access its contents.
*/
public static void copy(Resource resource, Content.Sink sink, ByteBufferPool bufferPool, int bufferSize, boolean direct, long first, long length, Callback callback) throws IllegalArgumentException
{
if (resource.isDirectory() || !resource.exists())
throw new IllegalArgumentException("Resource must exist and cannot be a directory: " + resource);
// Save a Content.Source allocation for resources with a Path.
Path path = resource.getPath();
if (path != null)
{
try
{
new PathToSinkCopier(path, sink, bufferPool, bufferSize, direct, first, length, callback).iterate();
}
catch (Throwable x)
{
callback.failed(x);
}
return;
}
// Directly write the byte array if the resource is a MemoryResource.
if (resource instanceof MemoryResource memoryResource)
{
byte[] bytes = memoryResource.getBytes();
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
if (first >= 0)
byteBuffer.position((int)first);
if (length >= 0)
byteBuffer.limit((int)(byteBuffer.position() + length));
sink.write(true, byteBuffer, callback);
return;
}
// Fallback to Content.Source.
Content.Source source = asContentSource(resource, bufferPool, bufferSize, direct, first, length);
Content.copy(source, sink, callback);
}
private static class PathToSinkCopier extends IteratingNestedCallback
{
private final SeekableByteChannel channel;
private final Content.Sink sink;
private final ByteBufferPool pool;
private final int bufferSize;
private final boolean direct;
private long remainingLength;
private RetainableByteBuffer retainableByteBuffer;
private boolean terminated;
public PathToSinkCopier(Path path, Content.Sink sink, ByteBufferPool pool, int bufferSize, boolean direct, Callback callback) throws IOException
{
this(path, sink, pool, bufferSize, direct, -1L, -1L, callback);
}
public PathToSinkCopier(Path path, Content.Sink sink, ByteBufferPool pool, int bufferSize, boolean direct, long first, long length, Callback callback) throws IOException
{
super(callback);
this.channel = Files.newByteChannel(path);
if (first > -1)
channel.position(first);
this.sink = sink;
this.pool = pool == null ? new ByteBufferPool.NonPooling() : pool;
this.bufferSize = bufferSize <= 0 ? 4096 : bufferSize;
this.direct = direct;
this.remainingLength = length;
}
@Override
public InvocationType getInvocationType()
{
return InvocationType.NON_BLOCKING;
}
@Override
protected Action process() throws Throwable
{
if (terminated)
return Action.SUCCEEDED;
if (retainableByteBuffer == null)
retainableByteBuffer = pool.acquire(bufferSize, direct);
ByteBuffer byteBuffer = retainableByteBuffer.getByteBuffer();
BufferUtil.clearToFill(byteBuffer);
if (remainingLength >= 0 && remainingLength < Integer.MAX_VALUE)
byteBuffer.limit((int)Math.min(byteBuffer.capacity(), remainingLength));
boolean eof = false;
while (byteBuffer.hasRemaining() && !eof)
{
int read = channel.read(byteBuffer);
if (read == -1)
eof = true;
else if (remainingLength >= 0)
remainingLength -= read;
}
BufferUtil.flipToFlush(byteBuffer, 0);
terminated = eof || remainingLength == 0;
sink.write(terminated, byteBuffer, this);
return Action.SCHEDULED;
}
@Override
protected void onCompleteSuccess()
{
if (retainableByteBuffer != null)
retainableByteBuffer.release();
IO.close(channel);
super.onCompleteSuccess();
}
@Override
protected void onCompleteFailure(Throwable x)
{
if (retainableByteBuffer != null)
retainableByteBuffer.release();
IO.close(channel);
super.onCompleteFailure(x);
}
}
/**
* <p>A specialized {@link PathContentSource}
* whose content is sliced by a byte range.</p>
*/
private static class RangedPathContentSource extends PathContentSource
{
private final long first;
private final long length;
private long toRead;
public RangedPathContentSource(Path path, ByteBufferPool bufferPool, long first, long length)
{
super(path, bufferPool);
// TODO perform sanity checks on first and length?
this.first = first;
this.length = length;
}
@Override
protected SeekableByteChannel open() throws IOException
{
SeekableByteChannel channel = super.open();
if (first > -1)
channel.position(first);
toRead = length;
return channel;
}
@Override
protected int read(SeekableByteChannel channel, ByteBuffer byteBuffer) throws IOException
{
int read = super.read(channel, byteBuffer);
if (read <= 0)
return read;
read = (int)Math.min(read, toRead);
if (read > -1)
{
toRead -= read;
byteBuffer.position(read);
}
return read;
}
@Override
protected boolean isReadComplete(long read)
{
return read == length;
}
}
/**
* <p>A specialized {@link InputStreamContentSource}
* whose content is sliced by a byte range.</p>
*/
private static class RangedInputStreamContentSource extends InputStreamContentSource
{
private long toRead;
public RangedInputStreamContentSource(InputStream inputStream, ByteBufferPool bufferPool, long first, long length) throws IOException
{
super(inputStream, bufferPool);
inputStream.skipNBytes(first);
// TODO perform sanity checks on length?
this.toRead = length;
}
@Override
protected int fillBufferFromInputStream(InputStream inputStream, byte[] buffer) throws IOException
{
if (toRead == 0)
return -1;
int toReadInt = (int)Math.min(Integer.MAX_VALUE, toRead);
int len = toReadInt > -1 ? Math.min(toReadInt, buffer.length) : buffer.length;
int read = inputStream.read(buffer, 0, len);
toRead -= read;
return read;
}
}
}

View File

@ -41,6 +41,7 @@ public class InputStreamContentSource implements Content.Source
private final InputStream inputStream;
private final ByteBufferPool bufferPool;
private int bufferSize = 4096;
private boolean useDirectByteBuffers;
private Runnable demandCallback;
private Content.Chunk errorChunk;
private boolean closed;
@ -66,6 +67,16 @@ public class InputStreamContentSource implements Content.Source
this.bufferSize = bufferSize;
}
public boolean isUseDirectByteBuffers()
{
return useDirectByteBuffers;
}
public void setUseDirectByteBuffers(boolean useDirectByteBuffers)
{
this.useDirectByteBuffers = useDirectByteBuffers;
}
@Override
public Content.Chunk read()
{
@ -77,7 +88,7 @@ public class InputStreamContentSource implements Content.Source
return Content.Chunk.EOF;
}
RetainableByteBuffer streamBuffer = bufferPool.acquire(getBufferSize(), false);
RetainableByteBuffer streamBuffer = bufferPool.acquire(getBufferSize(), useDirectByteBuffers);
try
{
ByteBuffer buffer = streamBuffer.getByteBuffer();

View File

@ -147,7 +147,7 @@ public class PathContentSource implements Content.Source
if (read > 0)
totalRead += read;
boolean last = isReadComplete(totalRead);
boolean last = read == -1 || isReadComplete(totalRead);
if (last)
IO.close(channel);

View File

@ -0,0 +1,182 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.io;
import java.net.URI;
import java.util.List;
import java.util.stream.Stream;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.util.resource.URLResourceFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
public class IOResourcesTest
{
private ArrayByteBufferPool.Tracking bufferPool;
@BeforeEach
public void setUp()
{
bufferPool = new ArrayByteBufferPool.Tracking();
}
@AfterEach
public void tearDown()
{
assertThat("Leaks: " + bufferPool.dumpLeaks(), bufferPool.getLeaks().size(), is(0));
}
public static Stream<Resource> all()
{
URI resourceUri = MavenTestingUtils.getTestResourcePath("keystore.p12").toUri();
return Stream.of(
ResourceFactory.root().newResource(resourceUri),
new URLResourceFactory().newResource(resourceUri)
);
}
@ParameterizedTest
@MethodSource("all")
public void testToRetainableByteBuffer(Resource resource)
{
RetainableByteBuffer retainableByteBuffer = IOResources.toRetainableByteBuffer(resource, bufferPool, false);
assertThat(retainableByteBuffer.remaining(), is((int)resource.length()));
retainableByteBuffer.release();
}
@ParameterizedTest
@MethodSource("all")
public void testAsContentSource(Resource resource) throws Exception
{
TestSink sink = new TestSink();
Callback.Completable callback = new Callback.Completable();
Content.Source contentSource = IOResources.asContentSource(resource, bufferPool, 1, false);
Content.copy(contentSource, sink, callback);
callback.get();
List<Content.Chunk> chunks = sink.takeAccumulatedChunks();
long sum = chunks.stream().mapToLong(Content.Chunk::remaining).sum();
assertThat(sum, is(resource.length()));
assertThat(chunks.get(chunks.size() - 1).isLast(), is(true));
}
@ParameterizedTest
@MethodSource("all")
public void testAsContentSourceWithFirst(Resource resource) throws Exception
{
TestSink sink = new TestSink();
Callback.Completable callback = new Callback.Completable();
Content.Source contentSource = IOResources.asContentSource(resource, bufferPool, 1, false, 100, -1);
Content.copy(contentSource, sink, callback);
callback.get();
List<Content.Chunk> chunks = sink.takeAccumulatedChunks();
long sum = chunks.stream().mapToLong(Content.Chunk::remaining).sum();
assertThat(sum, is(resource.length() - 100L));
assertThat(chunks.get(chunks.size() - 1).isLast(), is(true));
}
@ParameterizedTest
@MethodSource("all")
public void testAsContentSourceWithLength(Resource resource) throws Exception
{
TestSink sink = new TestSink();
Callback.Completable callback = new Callback.Completable();
Content.Source contentSource = IOResources.asContentSource(resource, bufferPool, 1, false, -1, 500);
Content.copy(contentSource, sink, callback);
callback.get();
List<Content.Chunk> chunks = sink.takeAccumulatedChunks();
long sum = chunks.stream().mapToLong(Content.Chunk::remaining).sum();
assertThat(sum, is(500L));
assertThat(chunks.get(chunks.size() - 1).isLast(), is(true));
}
@ParameterizedTest
@MethodSource("all")
public void testAsContentSourceWithFirstAndLength(Resource resource) throws Exception
{
TestSink sink = new TestSink();
Callback.Completable callback = new Callback.Completable();
Content.Source contentSource = IOResources.asContentSource(resource, bufferPool, 1, false, 100, 500);
Content.copy(contentSource, sink, callback);
callback.get();
List<Content.Chunk> chunks = sink.takeAccumulatedChunks();
long sum = chunks.stream().mapToLong(Content.Chunk::remaining).sum();
assertThat(sum, is(500L));
assertThat(chunks.get(chunks.size() - 1).isLast(), is(true));
}
@ParameterizedTest
@MethodSource("all")
public void testCopy(Resource resource) throws Exception
{
TestSink sink = new TestSink();
Callback.Completable callback = new Callback.Completable();
IOResources.copy(resource, sink, bufferPool, 1, false, callback);
callback.get();
List<Content.Chunk> chunks = sink.takeAccumulatedChunks();
long sum = chunks.stream().mapToLong(Content.Chunk::remaining).sum();
assertThat(sum, is(resource.length()));
assertThat(chunks.get(chunks.size() - 1).isLast(), is(true));
}
@ParameterizedTest
@MethodSource("all")
public void testCopyWithFirst(Resource resource) throws Exception
{
TestSink sink = new TestSink();
Callback.Completable callback = new Callback.Completable();
IOResources.copy(resource, sink, bufferPool, 1, false, 100, -1, callback);
callback.get();
List<Content.Chunk> chunks = sink.takeAccumulatedChunks();
long sum = chunks.stream().mapToLong(Content.Chunk::remaining).sum();
assertThat(sum, is(resource.length() - 100L));
assertThat(chunks.get(chunks.size() - 1).isLast(), is(true));
}
@ParameterizedTest
@MethodSource("all")
public void testCopyWithLength(Resource resource) throws Exception
{
TestSink sink = new TestSink();
Callback.Completable callback = new Callback.Completable();
IOResources.copy(resource, sink, bufferPool, 1, false, -1, 500, callback);
callback.get();
List<Content.Chunk> chunks = sink.takeAccumulatedChunks();
long sum = chunks.stream().mapToLong(Content.Chunk::remaining).sum();
assertThat(sum, is(500L));
assertThat(chunks.get(chunks.size() - 1).isLast(), is(true));
}
@ParameterizedTest
@MethodSource("all")
public void testCopyWithFirstAndLength(Resource resource) throws Exception
{
TestSink sink = new TestSink();
Callback.Completable callback = new Callback.Completable();
IOResources.copy(resource, sink, bufferPool, 1, false, 100, 500, callback);
callback.get();
List<Content.Chunk> chunks = sink.takeAccumulatedChunks();
long sum = chunks.stream().mapToLong(Content.Chunk::remaining).sum();
assertThat(sum, is(500L));
assertThat(chunks.get(chunks.size() - 1).isLast(), is(true));
}
}

View File

@ -17,6 +17,7 @@ import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
public class TestSink implements Content.Sink
@ -26,7 +27,7 @@ public class TestSink implements Content.Sink
@Override
public void write(boolean last, ByteBuffer byteBuffer, Callback callback)
{
accumulatedChunks.add(Content.Chunk.from(byteBuffer, last));
accumulatedChunks.add(Content.Chunk.from(BufferUtil.copy(byteBuffer), last));
callback.succeeded();
}

View File

@ -24,6 +24,7 @@ import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;
import org.eclipse.jetty.io.IOResources;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.util.security.Credential;
@ -96,7 +97,7 @@ public class JDBCLoginService extends AbstractLoginService
{
Properties properties = new Properties();
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable();
InputStream in = resourceFactory.newResource(_config).newInputStream())
InputStream in = IOResources.asInputStream(resourceFactory.newResource(_config)))
{
properties.load(in);
}

View File

@ -23,6 +23,7 @@ import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.eclipse.jetty.io.IOResources;
import org.eclipse.jetty.util.Scanner;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.resource.Resource;
@ -158,10 +159,8 @@ public class PropertyUserStore extends UserStore implements Scanner.DiscreteList
throw new IllegalStateException("Config does not exist: " + config);
Properties properties = new Properties();
try (InputStream inputStream = config.newInputStream())
try (InputStream inputStream = IOResources.asInputStream(config))
{
if (inputStream == null)
throw new IllegalStateException("Config does have properties: " + config);
properties.load(inputStream);
}

View File

@ -15,10 +15,8 @@ package org.eclipse.jetty.server;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -44,14 +42,10 @@ import org.eclipse.jetty.http.QuotedCSV;
import org.eclipse.jetty.http.QuotedQualityCSV;
import org.eclipse.jetty.http.content.HttpContent;
import org.eclipse.jetty.http.content.PreCompressedHttpContent;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.RetainableByteBuffer;
import org.eclipse.jetty.io.IOResources;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.IteratingCallback;
import org.eclipse.jetty.util.URIUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -686,15 +680,8 @@ public class ResourceService
response.setStatus(HttpStatus.PARTIAL_CONTENT_206);
response.getHeaders().put(HttpHeader.CONTENT_RANGE, range.toHeaderValue(contentLength));
// Try using the resource's path if possible, as the nio API is async and helps to avoid buffer copies.
Path path = content.getResource().getPath();
Content.Source source;
if (path != null)
source = new MultiPartByteRanges.PathContentSource(path, range);
else
source = new MultiPartByteRanges.InputStreamContentSource(content.getResource().newInputStream(), range);
Content.copy(source, response, callback);
// TODO use a buffer pool
IOResources.copy(content.getResource(), response, null, 0, false, range.first(), range.getLength(), callback);
return;
}
@ -703,7 +690,7 @@ public class ResourceService
String contentType = "multipart/byteranges; boundary=";
String boundary = MultiPart.generateBoundary(null, 24);
MultiPartByteRanges.ContentSource byteRanges = new MultiPartByteRanges.ContentSource(boundary);
ranges.forEach(range -> byteRanges.addPart(new MultiPartByteRanges.Part(content.getContentTypeValue(), content.getResource(), range, contentLength)));
ranges.forEach(range -> byteRanges.addPart(new MultiPartByteRanges.Part(content.getContentTypeValue(), content.getResource(), range, contentLength, request.getComponents().getByteBufferPool())));
byteRanges.close();
long partsContentLength = byteRanges.getLength();
putHeaders(response, content, partsContentLength);
@ -717,9 +704,18 @@ public class ResourceService
{
ByteBuffer buffer = content.getByteBuffer(); // this buffer is going to be consumed by response.write()
if (buffer != null)
{
response.write(true, buffer, callback);
}
else
new ContentWriterIteratingCallback(content, response, callback).iterate();
{
IOResources.copy(
content.getResource(),
response, request.getComponents().getByteBufferPool(),
request.getConnectionMetaData().getHttpConfiguration().getOutputBufferSize(),
request.getConnectionMetaData().getHttpConfiguration().isUseOutputDirectByteBuffers(),
callback);
}
}
catch (Throwable x)
{
@ -917,57 +913,4 @@ public class ResourceService
*/
String getWelcomeTarget(HttpContent content, Request request) throws IOException;
}
private static class ContentWriterIteratingCallback extends IteratingCallback
{
private final ReadableByteChannel source;
private final Content.Sink sink;
private final Callback callback;
private final RetainableByteBuffer buffer;
public ContentWriterIteratingCallback(HttpContent content, Response target, Callback callback) throws IOException
{
this.source = content.getResource().newReadableByteChannel();
this.sink = target;
this.callback = callback;
ByteBufferPool bufferPool = target.getRequest().getComponents().getByteBufferPool();
int outputBufferSize = target.getRequest().getConnectionMetaData().getHttpConfiguration().getOutputBufferSize();
boolean useOutputDirectByteBuffers = target.getRequest().getConnectionMetaData().getHttpConfiguration().isUseOutputDirectByteBuffers();
this.buffer = bufferPool.acquire(outputBufferSize, useOutputDirectByteBuffers);
}
@Override
protected Action process() throws Throwable
{
if (!source.isOpen())
return Action.SUCCEEDED;
ByteBuffer byteBuffer = buffer.getByteBuffer();
BufferUtil.clearToFill(byteBuffer);
int read = source.read(byteBuffer);
if (read == -1)
{
IO.close(source);
sink.write(true, BufferUtil.EMPTY_BUFFER, this);
return Action.SCHEDULED;
}
BufferUtil.flipToFlush(byteBuffer, 0);
sink.write(false, byteBuffer, this);
return Action.SCHEDULED;
}
@Override
protected void onCompleteSuccess()
{
buffer.release();
callback.succeeded();
}
@Override
protected void onCompleteFailure(Throwable x)
{
buffer.release();
callback.failed(x);
}
}
}

View File

@ -28,6 +28,7 @@ import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.io.IOResources;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
@ -97,7 +98,7 @@ public class DefaultHandler extends Handler.Abstract
Resource faviconRes = server.getDefaultFavicon();
if (faviconRes != null)
{
try (InputStream is = faviconRes.newInputStream())
try (InputStream is = IOResources.asInputStream(faviconRes))
{
favbytes = IO.readBytes(is);
}

View File

@ -27,6 +27,7 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import org.eclipse.jetty.io.ArrayByteBufferPool;
import org.eclipse.jetty.io.IOResources;
import org.eclipse.jetty.server.AbstractConnectionFactory;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
@ -106,7 +107,7 @@ public class DebugHandlerTest
secureServerURI = URI.create(String.format("https://%s:%d/", host, sslConnector.getLocalPort()));
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
try (InputStream stream = sslContextFactory.getKeyStoreResource().newInputStream())
try (InputStream stream = IOResources.asInputStream(sslContextFactory.getKeyStoreResource()))
{
keystore.load(stream, "storepwd".toCharArray());
}

View File

@ -28,7 +28,9 @@ import org.eclipse.jetty.http.MultiPart;
import org.eclipse.jetty.http.MultiPartByteRanges;
import org.eclipse.jetty.http.content.HttpContent;
import org.eclipse.jetty.http.content.ResourceHttpContent;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.IOResources;
import org.eclipse.jetty.io.content.ByteBufferContentSource;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.ResourceService;
@ -172,7 +174,7 @@ public class ResourceHandlerByteRangesTest
{
return path -> new ResourceHttpContent(memResource, "text/plain")
{
final ByteBuffer buffer = BufferUtil.toBuffer(getResource(), false);
final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), new ByteBufferPool.NonPooling(), false).getByteBuffer();
@Override
public ByteBuffer getByteBuffer()
@ -213,7 +215,7 @@ public class ResourceHandlerByteRangesTest
{
return path -> new ResourceHttpContent(memResource, "text/plain")
{
final ByteBuffer buffer = BufferUtil.toBuffer(getResource(), false);
final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), new ByteBufferPool.NonPooling(), false).getByteBuffer();
@Override
public ByteBuffer getByteBuffer()

View File

@ -30,6 +30,7 @@ import javax.net.ssl.TrustManagerFactory;
import org.eclipse.jetty.io.ArrayByteBufferPool;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.IOResources;
import org.eclipse.jetty.server.AbstractConnectionFactory;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
@ -88,7 +89,7 @@ public class ServerConnectorSslServerTest extends HttpServerTestBase
initServer(connector);
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
try (InputStream stream = sslContextFactory.getKeyStoreResource().newInputStream())
try (InputStream stream = IOResources.asInputStream(sslContextFactory.getKeyStoreResource()))
{
keystore.load(stream, "storepwd".toCharArray());
}

View File

@ -21,6 +21,7 @@ import java.nio.Buffer;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.channels.ReadableByteChannel;
@ -1102,7 +1103,7 @@ public class BufferUtil
ByteBuffer buffer = direct ? BufferUtil.allocateDirect(len) : BufferUtil.allocate(len);
int pos = BufferUtil.flipToFill(buffer);
try (ReadableByteChannel channel = resource.newReadableByteChannel())
try (ReadableByteChannel channel = Channels.newChannel(resource.newInputStream()))
{
long needed = len;
while (needed > 0 && buffer.hasRemaining())

View File

@ -55,6 +55,11 @@ public class MemoryResource extends Resource
}
}
public byte[] getBytes()
{
return _bytes;
}
@Override
public Path getPath()
{

View File

@ -347,8 +347,7 @@ public class PathResource extends Resource
}
catch (IOException e)
{
// in case of error, use Files.size() logic of 0L
return 0L;
return -1L;
}
}

View File

@ -238,7 +238,9 @@ public abstract class Resource implements Iterable<Resource>
*
* @return a readable {@link java.nio.channels.ByteChannel} to the resource or null if one is not available.
* @throws IOException if unable to open the readable bytechannel for the resource.
* @deprecated use {@link #newInputStream()} or {@code IOResources} instead.
*/
@Deprecated(since = "12.0.8", forRemoval = true)
public ReadableByteChannel newReadableByteChannel() throws IOException
{
Path path = getPath();

View File

@ -22,8 +22,6 @@ import java.io.StringReader;
import java.io.StringWriter;
import java.net.URI;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
@ -39,7 +37,6 @@ import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.IO;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.BufferUtil;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
@ -474,31 +471,6 @@ public class FileSystemResourceTest
}
}
@Test
public void testReadableByteChannel(WorkDir workDir) throws Exception
{
Path dir = workDir.getEmptyPathDir();
Path file = dir.resolve("foo");
String content = "Foo is here";
try (StringReader reader = new StringReader(content);
BufferedWriter writer = Files.newBufferedWriter(file))
{
IO.copy(reader, writer);
}
Resource base = ResourceFactory.root().newResource(dir);
Resource foo = base.resolve("foo");
try (ReadableByteChannel channel = foo.newReadableByteChannel())
{
ByteBuffer buf = ByteBuffer.allocate(256);
channel.read(buf);
buf.flip();
String actual = BufferUtil.toUTF8String(buf);
assertThat("ReadableByteChannel content", actual, is(content));
}
}
@Test
public void testGetURI(WorkDir workDir) throws Exception
{

View File

@ -16,7 +16,6 @@ package org.eclipse.jetty.util.resource;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.ClosedFileSystemException;
import java.nio.file.FileSystem;
@ -96,26 +95,6 @@ public class PathResourceTest
}
}
@Test
public void testNonDefaultFileSystemGetReadableByteChannel() throws IOException
{
Path exampleJar = MavenTestingUtils.getTestResourcePathFile("example.jar");
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
{
Resource jarFileResource = resourceFactory.newJarFileResource(exampleJar.toUri());
Path manifestPath = jarFileResource.getPath().resolve("/META-INF/MANIFEST.MF");
assertThat(manifestPath, is(not(nullValue())));
PathResource resource = (PathResource)resourceFactory.newResource(manifestPath);
try (ReadableByteChannel channel = resource.newReadableByteChannel())
{
assertThat("ReadableByteChannel", channel, is(not(nullValue())));
}
}
}
@Test
public void testNonDefaultFileSystemGetPath()
{

View File

@ -336,7 +336,7 @@ public class ResourceTest
}
@Test
public void testNonExistentResource()
public void testNonExistentResource() throws IOException
{
Path nonExistentFile = workDir.getPathFile("does-not-exists");
Resource resource = resourceFactory.newResource(nonExistentFile);
@ -348,7 +348,7 @@ public class ResourceTest
assertFalse(resource.isReadable());
assertEquals(nonExistentFile, resource.getPath());
assertEquals(Instant.EPOCH, resource.lastModified());
assertEquals(0L, resource.length());
assertEquals(-1L, resource.length());
assertThrows(IOException.class, resource::newInputStream);
assertThrows(IOException.class, resource::newReadableByteChannel);
assertEquals(nonExistentFile.toUri(), resource.getURI());

View File

@ -19,7 +19,6 @@ import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@ -31,6 +30,7 @@ import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.URIUtil;
import org.junit.jupiter.api.Tag;
@ -186,12 +186,8 @@ public class UrlResourceFactoryTest
assertThat(resource.isDirectory(), is(false));
try (ReadableByteChannel channel = resource.newReadableByteChannel())
{
ByteBuffer buffer = ByteBuffer.allocate(fileSize);
int read = channel.read(buffer);
assertThat(read, is(fileSize));
}
ByteBuffer buffer = BufferUtil.toBuffer(resource, false);
assertThat(buffer.remaining(), is(fileSize));
}
@Test
@ -213,12 +209,8 @@ public class UrlResourceFactoryTest
assertThat(resource.isDirectory(), is(false));
try (ReadableByteChannel channel = resource.newReadableByteChannel())
{
ByteBuffer buffer = ByteBuffer.allocate(fileSize);
int read = channel.read(buffer);
assertThat(read, is(fileSize));
}
ByteBuffer buffer = BufferUtil.toBuffer(resource, false);
assertThat(buffer.remaining(), is(fileSize));
}
@Test

View File

@ -16,7 +16,6 @@ package org.eclipse.jetty.ee10.maven.plugin;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Path;
import java.time.Instant;
import java.util.Collection;
@ -211,14 +210,6 @@ public class MavenResource extends Resource
return _resource.newInputStream();
}
@Override
public ReadableByteChannel newReadableByteChannel() throws IOException
{
if (_resource == null)
return null;
return _resource.newReadableByteChannel();
}
@Override
public Resource resolve(String subUriPath)
{

View File

@ -75,6 +75,7 @@ import org.eclipse.jetty.ee10.servlet.security.ConstraintMapping;
import org.eclipse.jetty.ee10.servlet.security.ConstraintSecurityHandler;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.pathmap.MatchedResource;
import org.eclipse.jetty.io.IOResources;
import org.eclipse.jetty.security.SecurityHandler;
import org.eclipse.jetty.server.Context;
import org.eclipse.jetty.server.FormFields;
@ -2843,7 +2844,7 @@ public class ServletContextHandler extends ContextHandler
// Cannot serve directories as an InputStream
if (r.isDirectory())
return null;
return r.newInputStream();
return IOResources.asInputStream(r);
}
catch (Exception e)
{

View File

@ -53,6 +53,8 @@ import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.http.UriCompliance;
import org.eclipse.jetty.http.content.ResourceHttpContent;
import org.eclipse.jetty.http.content.ResourceHttpContentFactory;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.IOResources;
import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.server.AllowedResourceAliasChecker;
import org.eclipse.jetty.server.HttpConfiguration;
@ -3468,7 +3470,7 @@ public class DefaultServletTest
context.addServlet(new ServletHolder(defaultServlet), "/");
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain")
{
final ByteBuffer buffer = BufferUtil.toBuffer(getResource(), false);
final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), new ByteBufferPool.NonPooling(), false).getByteBuffer();
@Override
public ByteBuffer getByteBuffer()
@ -3522,7 +3524,7 @@ public class DefaultServletTest
context.addServlet(new ServletHolder(defaultServlet), "/");
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain")
{
final ByteBuffer buffer = BufferUtil.toBuffer(getResource(), false);
final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), new ByteBufferPool.NonPooling(), false).getByteBuffer();
@Override
public ByteBuffer getByteBuffer()

View File

@ -14,7 +14,6 @@
package org.eclipse.jetty.ee10.session;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import jakarta.servlet.http.HttpServletResponse;
@ -29,7 +28,6 @@ import org.eclipse.jetty.session.DefaultSessionCacheFactory;
import org.eclipse.jetty.session.SessionCache;
import org.eclipse.jetty.session.SessionDataStoreFactory;
import org.eclipse.jetty.session.test.AbstractSessionTestBase;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.junit.jupiter.api.Test;
@ -87,14 +85,14 @@ public abstract class AbstractWebAppObjectInSessionTest extends AbstractSessionT
//File sourceFile = new File(getClass().getClassLoader().getResource(resourceName).toURI());
File targetFile = new File(packageDirs, resourceName);
//copy(sourceFile, targetFile);
IO.copy(resource.newInputStream(), new FileOutputStream(targetFile));
resource.copyTo(targetFile.toPath());
resourceName = WebAppObjectInSessionServlet.class.getSimpleName() + "$" + WebAppObjectInSessionServlet.TestSharedStatic.class.getSimpleName() + ".class";
resource = resourceFactory.newResource(getClass().getResource(packageName + resourceName));
//sourceFile = new File(getClass().getClassLoader().getResource(resourceName).toURI());
targetFile = new File(packageDirs, resourceName);
//copy(sourceFile, targetFile);
IO.copy(resource.newInputStream(), new FileOutputStream(targetFile));
resource.copyTo(targetFile.toPath());
}
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();

View File

@ -17,6 +17,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;
import org.eclipse.jetty.io.IOResources;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.xml.XmlParser;
import org.slf4j.Logger;
@ -44,7 +45,7 @@ public abstract class Descriptor
if (_root == null)
{
Objects.requireNonNull(parser);
try (InputStream is = _xml.newInputStream())
try (InputStream is = IOResources.asInputStream(_xml))
{
_root = parser.parse(is);
_dtd = parser.getDTD();

View File

@ -16,7 +16,6 @@ package org.eclipse.jetty.ee9.maven.plugin;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Path;
import java.time.Instant;
import java.util.Collection;
@ -211,14 +210,6 @@ public class MavenResource extends Resource
return _resource.newInputStream();
}
@Override
public ReadableByteChannel newReadableByteChannel() throws IOException
{
if (_resource == null)
return null;
return _resource.newReadableByteChannel();
}
@Override
public Resource resolve(String subUriPath)
{

View File

@ -66,6 +66,7 @@ import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.io.IOResources;
import org.eclipse.jetty.server.AliasCheck;
import org.eclipse.jetty.server.AllowedResourceAliasChecker;
import org.eclipse.jetty.server.Context;
@ -1980,7 +1981,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Supplie
// Cannot serve directories as an InputStream
if (r.isDirectory())
return null;
return r.newInputStream();
return IOResources.asInputStream(r);
}
catch (Exception e)
{

View File

@ -33,6 +33,7 @@ import jakarta.servlet.ServletResponse;
import jakarta.servlet.WriteListener;
import org.eclipse.jetty.http.content.HttpContent;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.io.IOResources;
import org.eclipse.jetty.io.RetainableByteBuffer;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.util.BufferUtil;
@ -43,6 +44,7 @@ import org.eclipse.jetty.util.IteratingCallback;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.SharedBlockingCallback;
import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.ThreadIdPool;
import org.slf4j.Logger;
@ -1166,7 +1168,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
try (Blocker blocker = _writeBlocker.acquire())
{
new InputStreamWritingCB(in, blocker).iterate();
sendContent(in, blocker);
blocker.block();
}
}
@ -1181,7 +1183,22 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
try (Blocker blocker = _writeBlocker.acquire())
{
new ReadableByteChannelWritingCB(in, blocker).iterate();
sendContent(in, blocker);
blocker.block();
}
}
/**
* Blocking send of resource.
*
* @param resource The resource content to send
* @throws IOException if the send fails
*/
public void sendContent(Resource resource) throws IOException
{
try (Blocker blocker = _writeBlocker.acquire())
{
sendContent(resource, blocker);
blocker.block();
}
}
@ -1213,8 +1230,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
LOG.debug("sendContent(buffer={},{})", BufferUtil.toDetailString(content), callback);
if (prepareSendContent(content.remaining(), callback))
channelWrite(content, true,
new Callback.Nested(callback)
{
channelWrite(content, true, new Callback.Nested(callback)
{
@Override
public void succeeded()
@ -1231,6 +1248,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
}
});
}
}
/**
* Asynchronous send of stream content.
@ -1264,6 +1282,67 @@ public class HttpOutput extends ServletOutputStream implements Runnable
new ReadableByteChannelWritingCB(in, callback).iterate();
}
/**
* Asynchronous send of whole resource.
*
* @param resource The resource content to send
* @param callback The callback to use to notify success or failure
*/
public void sendContent(Resource resource, Callback callback)
{
try
{
if (prepareSendContent(0, callback))
{
IOResources.copy(resource, (last, byteBuffer, cb) ->
{
_written += byteBuffer.remaining();
channelWrite(byteBuffer, last, cb);
}, _channel.getByteBufferPool(), getBufferSize(), _channel.isUseOutputDirectByteBuffers(), new Callback.Nested(callback)
{
@Override
public void succeeded()
{
onWriteComplete(true, null);
super.succeeded();
}
@Override
public void failed(Throwable x)
{
onWriteComplete(true, x);
super.failed(x);
}
});
}
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Unable to send resource {}", resource, x);
_channel.abort(x);
callback.failed(x);
}
}
/**
* Asynchronous send of HTTP content.
*
* @param httpContent The HTTP content to send
* @param callback The callback to use to notify success or failure
*/
public void sendContent(HttpContent httpContent, Callback callback)
{
if (LOG.isDebugEnabled())
LOG.debug("sendContent(http={},{})", httpContent, callback);
ByteBuffer buffer = httpContent.getByteBuffer();
if (buffer != null)
sendContent(buffer, callback);
else
sendContent(httpContent.getResource(), callback);
}
private boolean prepareSendContent(int len, Callback callback)
{
try (AutoLock l = _channelState.lock())
@ -1306,38 +1385,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
}
}
/**
* Asynchronous send of HTTP content.
*
* @param httpContent The HTTP content to send
* @param callback The callback to use to notify success or failure
*/
public void sendContent(HttpContent httpContent, Callback callback)
{
if (LOG.isDebugEnabled())
LOG.debug("sendContent(http={},{})", httpContent, callback);
ByteBuffer buffer = httpContent.getByteBuffer();
if (buffer != null)
{
sendContent(buffer, callback);
return;
}
try
{
ReadableByteChannel rbc = httpContent.getResource().newReadableByteChannel();
sendContent(rbc, callback);
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Unable to access ReadableByteChannel for content {}", httpContent, x);
_channel.abort(x);
callback.failed(x);
}
}
public int getBufferSize()
{
return _bufferSize;

View File

@ -50,6 +50,7 @@ import org.eclipse.jetty.http.QuotedCSV;
import org.eclipse.jetty.http.QuotedQualityCSV;
import org.eclipse.jetty.http.content.HttpContent;
import org.eclipse.jetty.http.content.PreCompressedHttpContent;
import org.eclipse.jetty.io.IOResources;
import org.eclipse.jetty.io.WriterOutputStream;
import org.eclipse.jetty.server.ResourceListing;
import org.eclipse.jetty.util.BufferUtil;
@ -897,7 +898,7 @@ public class ResourceService
}
// Perform ranged write
try (InputStream input = content.getResource().newInputStream())
try (InputStream input = IOResources.asInputStream(content.getResource()))
{
input.skipNBytes(start);
IO.copy(input, out, contentLength);

View File

@ -19,6 +19,7 @@ import java.nio.file.Path;
import java.util.Objects;
import org.eclipse.jetty.http.content.HttpContent;
import org.eclipse.jetty.io.IOResources;
/**
* Range Writer selection for HttpContent
@ -46,6 +47,6 @@ public class HttpContentRangeWriter
return new SeekableByteChannelRangeWriter(() -> Files.newByteChannel(path));
// Fallback to InputStream
return new InputStreamRangeWriter(() -> content.getResource().newInputStream());
return new InputStreamRangeWriter(() -> IOResources.asInputStream(content.getResource()));
}
}

View File

@ -114,6 +114,7 @@ public class InputStreamRangeWriter implements RangeWriter
pos = skipTo;
}
// TODO this is very inefficient as copy() allocates a 64K buffer.
IO.copy(inputStream, outputStream, length);
pos += length;
}

View File

@ -21,6 +21,7 @@ import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@ -31,6 +32,7 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.WriteListener;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.io.IOResources;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.LocalConnector.LocalEndPoint;
@ -138,7 +140,7 @@ public class HttpOutputTest
public void testSendInputStreamSimple() throws Exception
{
Resource simple = ResourceFactory.of(_contextHandler).newClassPathResource("simple/simple.txt");
_handler._contentInputStream = simple.newInputStream();
_handler._contentInputStream = IOResources.asInputStream(simple);
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, containsString("Content-Length: 11"));
@ -148,7 +150,7 @@ public class HttpOutputTest
public void testSendInputStreamBig() throws Exception
{
Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._contentInputStream = big.newInputStream();
_handler._contentInputStream = IOResources.asInputStream(big);
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, Matchers.not(containsString("Content-Length")));
@ -159,7 +161,7 @@ public class HttpOutputTest
public void testSendInputStreamBigChunked() throws Exception
{
Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._contentInputStream = new FilterInputStream(big.newInputStream())
_handler._contentInputStream = new FilterInputStream(IOResources.asInputStream(big))
{
@Override
public int read(byte[] b, int off, int len) throws IOException
@ -190,7 +192,7 @@ public class HttpOutputTest
public void testSendChannelSimple() throws Exception
{
Resource simple = ResourceFactory.of(_contextHandler).newClassPathResource("simple/simple.txt");
_handler._contentChannel = simple.newReadableByteChannel();
_handler._contentChannel = Files.newByteChannel(simple.getPath());
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, containsString("Content-Length: 11"));
@ -200,7 +202,29 @@ public class HttpOutputTest
public void testSendChannelBig() throws Exception
{
Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._contentChannel = big.newReadableByteChannel();
_handler._contentChannel = Files.newByteChannel(big.getPath());
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, Matchers.not(containsString("Content-Length")));
assertThat(response, endsWith(toUTF8String(big)));
}
@Test
public void testSendResourceSimple() throws Exception
{
Resource simple = ResourceFactory.of(_contextHandler).newClassPathResource("simple/simple.txt");
_handler._contentResource = simple;
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, containsString("Content-Length: 11"));
assertThat(response, endsWith(toUTF8String(simple)));
}
@Test
public void testSendResourceBig() throws Exception
{
Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._contentResource = big;
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, Matchers.not(containsString("Content-Length")));
@ -211,7 +235,7 @@ public class HttpOutputTest
public void testSendBigDirect() throws Exception
{
Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._content = BufferUtil.toBuffer(big, true);
_handler._content = IOResources.toRetainableByteBuffer(big, null, true).getByteBuffer();
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, containsString("Content-Length"));
@ -222,7 +246,7 @@ public class HttpOutputTest
public void testSendBigInDirect() throws Exception
{
Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, containsString("Content-Length"));
@ -233,7 +257,7 @@ public class HttpOutputTest
public void testSendChannelBigChunked() throws Exception
{
Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
final ReadableByteChannel channel = big.newReadableByteChannel();
final ReadableByteChannel channel = Files.newByteChannel(big.getPath());
_handler._contentChannel = new ReadableByteChannel()
{
@Override
@ -287,7 +311,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = false;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._arrayBuffer = new byte[1];
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
@ -301,7 +325,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = false;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._arrayBuffer = new byte[8];
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
@ -315,7 +339,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = false;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._arrayBuffer = new byte[4000];
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
@ -329,7 +353,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = false;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._arrayBuffer = new byte[8192];
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
@ -343,7 +367,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = true;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._arrayBuffer = new byte[1];
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
@ -358,7 +382,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = true;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._arrayBuffer = new byte[8];
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
@ -373,7 +397,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = true;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._arrayBuffer = new byte[4000];
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
@ -388,7 +412,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = true;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._arrayBuffer = new byte[8192];
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
@ -421,7 +445,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = false;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._byteBuffer = BufferUtil.allocate(8);
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
@ -436,7 +460,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = false;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._byteBuffer = BufferUtil.allocate(4000);
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
@ -451,7 +475,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = false;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._byteBuffer = BufferUtil.allocate(8192);
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
@ -466,7 +490,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = true;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._byteBuffer = BufferUtil.allocate(8);
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
@ -481,7 +505,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = true;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._byteBuffer = BufferUtil.allocate(4000);
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
@ -496,7 +520,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = true;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._byteBuffer = BufferUtil.allocate(8192);
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
@ -511,7 +535,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = false;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._arrayBuffer = new byte[1];
_handler._async = true;
@ -527,7 +551,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = false;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._arrayBuffer = new byte[8];
_handler._async = true;
@ -543,7 +567,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = false;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._arrayBuffer = new byte[4000];
_handler._async = true;
@ -559,7 +583,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = false;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._arrayBuffer = new byte[8192];
_handler._async = true;
@ -594,7 +618,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = false;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._byteBuffer = BufferUtil.allocate(8);
_handler._async = true;
@ -610,7 +634,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = false;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._byteBuffer = BufferUtil.allocate(4000);
_handler._async = true;
@ -626,7 +650,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = false;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._byteBuffer = BufferUtil.allocate(8192);
_handler._async = true;
@ -643,7 +667,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = false;
_handler._content = BufferUtil.toBuffer(big, true);
_handler._content = IOResources.toRetainableByteBuffer(big, null, true).getByteBuffer();
_handler._byteBuffer = BufferUtil.allocateDirect(8192);
_handler._async = true;
@ -659,7 +683,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = false;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._byteBuffer = BufferUtil.allocate(8192);
_handler._async = true;
@ -679,7 +703,7 @@ public class HttpOutputTest
_handler._async = true;
_handler._writeLengthIfKnown = true;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._arrayBuffer = new byte[4000];
String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n");
@ -696,7 +720,7 @@ public class HttpOutputTest
_handler._async = true;
_handler._writeLengthIfKnown = true;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._arrayBuffer = new byte[4000];
int start = _handler._owp.get();
@ -712,7 +736,7 @@ public class HttpOutputTest
{
final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown = false;
_handler._content = BufferUtil.toBuffer(big, false);
_handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer();
_handler._arrayBuffer = new byte[1024];
_handler._interceptor = new ChainedInterceptor()
{
@ -1169,9 +1193,8 @@ public class HttpOutputTest
}
private static String toUTF8String(Resource resource)
throws IOException
{
return BufferUtil.toUTF8String(BufferUtil.toBuffer(resource, false));
return BufferUtil.toUTF8String(IOResources.toRetainableByteBuffer(resource, null, false).getByteBuffer());
}
interface ChainedInterceptor extends HttpOutput.Interceptor
@ -1192,6 +1215,7 @@ public class HttpOutputTest
byte[] _arrayBuffer;
InputStream _contentInputStream;
ReadableByteChannel _contentChannel;
Resource _contentResource;
ByteBuffer _content;
ChainedInterceptor _interceptor;
final FuturePromise<Boolean> _closedAfterWrite = new FuturePromise<>();
@ -1227,6 +1251,14 @@ public class HttpOutputTest
return;
}
if (_contentResource != null)
{
out.sendContent(_contentResource);
_contentResource = null;
_closedAfterWrite.succeeded(out.isClosed());
return;
}
if (_content != null && _writeLengthIfKnown)
response.setContentLength(_content.remaining());

View File

@ -48,6 +48,7 @@ import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.http.UriCompliance;
import org.eclipse.jetty.http.content.ResourceHttpContent;
import org.eclipse.jetty.io.IOResources;
import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.server.AllowedResourceAliasChecker;
import org.eclipse.jetty.server.HttpConfiguration;
@ -59,7 +60,6 @@ import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.resource.FileSystemPool;
import org.eclipse.jetty.util.resource.Resource;
@ -2339,7 +2339,7 @@ public class DefaultServletTest
ResourceService resourceService = new ResourceService();
resourceService.setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain")
{
final ByteBuffer buffer = BufferUtil.toBuffer(getResource(), false);
final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), null, false).getByteBuffer();
@Override
public ByteBuffer getByteBuffer()
@ -2370,7 +2370,7 @@ public class DefaultServletTest
ResourceService resourceService = new ResourceService();
resourceService.setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain")
{
final ByteBuffer buffer = BufferUtil.toBuffer(getResource(), false);
final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), null, false).getByteBuffer();
@Override
public ByteBuffer getByteBuffer()

View File

@ -14,7 +14,6 @@
package org.eclipse.jetty.ee9.session;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import jakarta.servlet.http.HttpServletResponse;
@ -29,7 +28,6 @@ import org.eclipse.jetty.session.DefaultSessionCacheFactory;
import org.eclipse.jetty.session.SessionCache;
import org.eclipse.jetty.session.SessionDataStoreFactory;
import org.eclipse.jetty.session.test.AbstractSessionTestBase;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.junit.jupiter.api.Test;
@ -87,14 +85,14 @@ public abstract class AbstractWebAppObjectInSessionTest extends AbstractSessionT
//File sourceFile = new File(getClass().getClassLoader().getResource(resourceName).toURI());
File targetFile = new File(packageDirs, resourceName);
//copy(sourceFile, targetFile);
IO.copy(resource.newInputStream(), new FileOutputStream(targetFile));
resource.copyTo(targetFile.toPath());
resourceName = WebAppObjectInSessionServlet.class.getSimpleName() + "$" + WebAppObjectInSessionServlet.TestSharedStatic.class.getSimpleName() + ".class";
resource = resourceFactory.newResource(getClass().getResource(packageName + resourceName));
//sourceFile = new File(getClass().getClassLoader().getResource(resourceName).toURI());
targetFile = new File(packageDirs, resourceName);
//copy(sourceFile, targetFile);
IO.copy(resource.newInputStream(), new FileOutputStream(targetFile));
resource.copyTo(targetFile.toPath());
}
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();

View File

@ -17,6 +17,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;
import org.eclipse.jetty.io.IOResources;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.xml.XmlParser;
import org.slf4j.Logger;
@ -44,7 +45,7 @@ public abstract class Descriptor
if (_root == null)
{
Objects.requireNonNull(parser);
try (InputStream is = _xml.newInputStream())
try (InputStream is = IOResources.asInputStream(_xml))
{
_root = parser.parse(is);
_dtd = parser.getDTD();