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:
parent
48f6ab7289
commit
89c41b2550
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 < 1 means use a default value.
|
||||
* @param direct the directness of the buffers, this parameter is ignored if {@code bufferSize} is < 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 < 1 means use a default value.
|
||||
* @param direct the directness of the buffers, this parameter is ignored if {@code bufferSize} is < 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 < 1 means use a default value.
|
||||
* @param direct the directness of the buffers, this parameter is ignored if {@code bufferSize} is < 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 < 1 means use a default value.
|
||||
* @param direct the directness of the buffers, this parameter is ignored if {@code bufferSize} is < 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -55,6 +55,11 @@ public class MemoryResource extends Resource
|
|||
}
|
||||
}
|
||||
|
||||
public byte[] getBytes()
|
||||
{
|
||||
return _bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getPath()
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue