Implement HttpContent.writeTo() async API (#12020)
#8790 implement HttpContent.writeTo() async API Signed-off-by: Ludovic Orban <lorban@bitronix.be>
This commit is contained in:
parent
d43da5279c
commit
834db77605
|
@ -30,28 +30,52 @@ public class InputStreamRequestContent extends InputStreamContentSource implemen
|
|||
{
|
||||
private final String contentType;
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #InputStreamRequestContent(String, InputStream, ByteBufferPool.Sized)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public InputStreamRequestContent(InputStream stream)
|
||||
{
|
||||
this(stream, 4096);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #InputStreamRequestContent(String, InputStream, ByteBufferPool.Sized)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public InputStreamRequestContent(InputStream stream, int bufferSize)
|
||||
{
|
||||
this("application/octet-stream", stream, bufferSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #InputStreamRequestContent(String, InputStream, ByteBufferPool.Sized)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public InputStreamRequestContent(String contentType, InputStream stream, int bufferSize)
|
||||
{
|
||||
this(contentType, stream);
|
||||
setBufferSize(bufferSize);
|
||||
this(contentType, stream, new ByteBufferPool.Sized(null, false, bufferSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #InputStreamRequestContent(String, InputStream, ByteBufferPool.Sized)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public InputStreamRequestContent(String contentType, InputStream stream)
|
||||
{
|
||||
this(contentType, stream, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #InputStreamRequestContent(String, InputStream, ByteBufferPool.Sized)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public InputStreamRequestContent(String contentType, InputStream stream, ByteBufferPool bufferPool)
|
||||
{
|
||||
this(contentType, stream, new ByteBufferPool.Sized(bufferPool));
|
||||
}
|
||||
|
||||
public InputStreamRequestContent(String contentType, InputStream stream, ByteBufferPool.Sized bufferPool)
|
||||
{
|
||||
super(stream, bufferPool);
|
||||
this.contentType = contentType;
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
package org.eclipse.jetty.http.content;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Instant;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
|
@ -30,9 +29,12 @@ 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.Content;
|
||||
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.Callback;
|
||||
import org.eclipse.jetty.util.NanoTime;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
|
@ -328,30 +330,19 @@ public class CachingHttpContentFactory implements HttpContent.Factory
|
|||
boolean isValid = true;
|
||||
|
||||
// Read the content into memory if the HttpContent does not already have a buffer.
|
||||
RetainableByteBuffer buffer;
|
||||
ByteBuffer byteBuffer = httpContent.getByteBuffer();
|
||||
if (byteBuffer == null)
|
||||
RetainableByteBuffer buffer = null;
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_contentLengthValue <= _maxCachedFileSize)
|
||||
buffer = IOResources.toRetainableByteBuffer(httpContent.getResource(), _bufferPool, _useDirectByteBuffers);
|
||||
else
|
||||
buffer = null;
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
buffer = null;
|
||||
isValid = false;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.warn("Failed to read Resource: {}", httpContent.getResource(), t);
|
||||
else
|
||||
LOG.warn("Failed to read Resource: {} - {}", httpContent.getResource(), t.toString());
|
||||
}
|
||||
if (_contentLengthValue <= _maxCachedFileSize)
|
||||
buffer = IOResources.toRetainableByteBuffer(httpContent.getResource(), _bufferPool, _useDirectByteBuffers);
|
||||
}
|
||||
else
|
||||
catch (Throwable t)
|
||||
{
|
||||
buffer = RetainableByteBuffer.wrap(byteBuffer);
|
||||
isValid = false;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.warn("Failed to read Resource: {}", httpContent.getResource(), t);
|
||||
else
|
||||
LOG.warn("Failed to read Resource: {} - {}", httpContent.getResource(), t.toString());
|
||||
}
|
||||
|
||||
_buffer = buffer;
|
||||
|
@ -373,12 +364,6 @@ public class CachingHttpContentFactory implements HttpContent.Factory
|
|||
return _contentLengthValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer getByteBuffer()
|
||||
{
|
||||
return _buffer == null ? null : _buffer.getByteBuffer().asReadOnlyBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBytesOccupied()
|
||||
{
|
||||
|
@ -403,6 +388,20 @@ public class CachingHttpContentFactory implements HttpContent.Factory
|
|||
return _cacheKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(Content.Sink sink, long offset, long length, Callback callback)
|
||||
{
|
||||
try
|
||||
{
|
||||
sink.write(true, BufferUtil.slice(_buffer.getByteBuffer(), (int)offset, (int)length), callback);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
// BufferUtil.slice() may fail if offset and/or length are out of bounds.
|
||||
callback.failed(x);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retain()
|
||||
{
|
||||
|
@ -596,9 +595,9 @@ public class CachingHttpContentFactory implements HttpContent.Factory
|
|||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer getByteBuffer()
|
||||
public void writeTo(Content.Sink sink, long offset, long length, Callback callback)
|
||||
{
|
||||
return null;
|
||||
sink.write(true, BufferUtil.EMPTY_BUFFER, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -15,10 +15,13 @@ package org.eclipse.jetty.http.content;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IteratingNestedCallback;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -26,9 +29,11 @@ public class FileMappingHttpContentFactory implements HttpContent.Factory
|
|||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FileMappingHttpContentFactory.class);
|
||||
private static final int DEFAULT_MIN_FILE_SIZE = 16 * 1024;
|
||||
private static final int DEFAULT_MAX_BUFFER_SIZE = Integer.MAX_VALUE;
|
||||
|
||||
private final HttpContent.Factory _factory;
|
||||
private final int _minFileSize;
|
||||
private final int _maxBufferSize;
|
||||
|
||||
/**
|
||||
* Construct a {@link FileMappingHttpContentFactory} which can use file mapped buffers.
|
||||
|
@ -39,7 +44,7 @@ public class FileMappingHttpContentFactory implements HttpContent.Factory
|
|||
*/
|
||||
public FileMappingHttpContentFactory(HttpContent.Factory factory)
|
||||
{
|
||||
this(factory, DEFAULT_MIN_FILE_SIZE);
|
||||
this(factory, DEFAULT_MIN_FILE_SIZE, DEFAULT_MAX_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -47,11 +52,13 @@ public class FileMappingHttpContentFactory implements HttpContent.Factory
|
|||
*
|
||||
* @param factory the wrapped {@link HttpContent.Factory} to use.
|
||||
* @param minFileSize the minimum size of an {@link HttpContent} before trying to use a file mapped buffer.
|
||||
* @param maxBufferSize the maximum size of the memory mapped buffers
|
||||
*/
|
||||
public FileMappingHttpContentFactory(HttpContent.Factory factory, int minFileSize)
|
||||
public FileMappingHttpContentFactory(HttpContent.Factory factory, int minFileSize, int maxBufferSize)
|
||||
{
|
||||
_factory = Objects.requireNonNull(factory);
|
||||
_minFileSize = minFileSize;
|
||||
_maxBufferSize = maxBufferSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -60,71 +67,147 @@ public class FileMappingHttpContentFactory implements HttpContent.Factory
|
|||
HttpContent content = _factory.getContent(path);
|
||||
if (content != null)
|
||||
{
|
||||
long contentLength = content.getContentLengthValue();
|
||||
if (contentLength > _minFileSize && contentLength < Integer.MAX_VALUE)
|
||||
return new FileMappedHttpContent(content);
|
||||
try
|
||||
{
|
||||
long contentLength = content.getContentLengthValue();
|
||||
if (contentLength < _minFileSize)
|
||||
return content;
|
||||
return contentLength <= _maxBufferSize ? new SingleBufferFileMappedHttpContent(content) : new MultiBufferFileMappedHttpContent(content, _maxBufferSize);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Error getting Mapped Buffer", e);
|
||||
// Fall through to return the content gotten from the factory.
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
private static class FileMappedHttpContent extends HttpContent.Wrapper
|
||||
private static class SingleBufferFileMappedHttpContent extends HttpContent.Wrapper
|
||||
{
|
||||
private static final ByteBuffer SENTINEL_BUFFER = BufferUtil.allocate(0);
|
||||
private final ByteBuffer _buffer;
|
||||
|
||||
private final AutoLock _lock = new AutoLock();
|
||||
private final HttpContent _content;
|
||||
private volatile ByteBuffer _buffer;
|
||||
|
||||
public FileMappedHttpContent(HttpContent content)
|
||||
private SingleBufferFileMappedHttpContent(HttpContent content) throws IOException
|
||||
{
|
||||
super(content);
|
||||
this._content = content;
|
||||
Path path = content.getResource().getPath();
|
||||
if (path == null)
|
||||
throw new IOException("Cannot memory map Content whose Resource is not backed by a Path: " + content.getResource());
|
||||
_buffer = BufferUtil.toMappedBuffer(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer getByteBuffer()
|
||||
public void writeTo(Content.Sink sink, long offset, long length, Callback callback)
|
||||
{
|
||||
ByteBuffer buffer = _buffer;
|
||||
if (buffer != null)
|
||||
return (buffer == SENTINEL_BUFFER) ? super.getByteBuffer() : buffer.asReadOnlyBuffer();
|
||||
|
||||
try (AutoLock lock = _lock.lock())
|
||||
try
|
||||
{
|
||||
if (_buffer == null)
|
||||
_buffer = getMappedByteBuffer();
|
||||
return (_buffer == SENTINEL_BUFFER) ? super.getByteBuffer() : _buffer.asReadOnlyBuffer();
|
||||
sink.write(true, BufferUtil.slice(_buffer, (int)offset, (int)length), callback);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
callback.failed(x);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBytesOccupied()
|
||||
{
|
||||
ByteBuffer buffer = _buffer;
|
||||
if (buffer != null)
|
||||
return (buffer == SENTINEL_BUFFER) ? super.getBytesOccupied() : 0;
|
||||
return _buffer.remaining();
|
||||
}
|
||||
}
|
||||
|
||||
try (AutoLock lock = _lock.lock())
|
||||
private static class MultiBufferFileMappedHttpContent extends HttpContent.Wrapper
|
||||
{
|
||||
private final ByteBuffer[] _buffers;
|
||||
private final int maxBufferSize;
|
||||
private final long _bytesOccupied;
|
||||
|
||||
private MultiBufferFileMappedHttpContent(HttpContent content, int maxBufferSize) throws IOException
|
||||
{
|
||||
super(content);
|
||||
this.maxBufferSize = maxBufferSize;
|
||||
Path path = content.getResource().getPath();
|
||||
if (path == null)
|
||||
throw new IOException("Cannot memory map Content whose Resource is not backed by a Path: " + content.getResource());
|
||||
|
||||
long contentLength = content.getContentLengthValue();
|
||||
int bufferCount = Math.toIntExact(contentLength / maxBufferSize);
|
||||
_buffers = new ByteBuffer[bufferCount];
|
||||
long currentPos = 0L;
|
||||
long total = 0L;
|
||||
for (int i = 0; i < _buffers.length; i++)
|
||||
{
|
||||
if (_buffer == null)
|
||||
_buffer = getMappedByteBuffer();
|
||||
return (_buffer == SENTINEL_BUFFER) ? super.getBytesOccupied() : 0;
|
||||
long len = Math.min(contentLength - currentPos, maxBufferSize);
|
||||
_buffers[i] = BufferUtil.toMappedBuffer(path, currentPos, len);
|
||||
currentPos += len;
|
||||
total += _buffers[i].remaining();
|
||||
}
|
||||
_bytesOccupied = total;
|
||||
}
|
||||
|
||||
private ByteBuffer getMappedByteBuffer()
|
||||
@Override
|
||||
public void writeTo(Content.Sink sink, long offset, long length, Callback callback)
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteBuffer byteBuffer = BufferUtil.toMappedBuffer(_content.getResource().getPath());
|
||||
return (byteBuffer == null) ? SENTINEL_BUFFER : byteBuffer;
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Error getting Mapped Buffer", t);
|
||||
}
|
||||
if (offset > getBytesOccupied())
|
||||
throw new IllegalArgumentException("Offset outside of mapped file range");
|
||||
if (length > -1 && length + offset > getBytesOccupied())
|
||||
throw new IllegalArgumentException("Offset / length outside of mapped file range");
|
||||
|
||||
return SENTINEL_BUFFER;
|
||||
int beginIndex = Math.toIntExact(offset / maxBufferSize);
|
||||
int firstOffset = Math.toIntExact(offset % maxBufferSize);
|
||||
|
||||
int endIndex = calculateEndIndex(offset, length);
|
||||
int lastLen = calculateLastLen(offset, length);
|
||||
new IteratingNestedCallback(callback)
|
||||
{
|
||||
int index = beginIndex;
|
||||
@Override
|
||||
protected Action process()
|
||||
{
|
||||
if (index > endIndex)
|
||||
return Action.SUCCEEDED;
|
||||
|
||||
ByteBuffer currentBuffer = _buffers[index];
|
||||
int offset = index == beginIndex ? firstOffset : 0;
|
||||
int len = index == endIndex ? lastLen : -1;
|
||||
boolean last = index == endIndex;
|
||||
index++;
|
||||
sink.write(last, BufferUtil.slice(currentBuffer, offset, len), this);
|
||||
return Action.SCHEDULED;
|
||||
}
|
||||
}.iterate();
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
callback.failed(x);
|
||||
}
|
||||
}
|
||||
|
||||
private int calculateLastLen(long offset, long length)
|
||||
{
|
||||
if (length == 0)
|
||||
return 0;
|
||||
int lastLen = length < 0 ? -1 : Math.toIntExact((length + offset) % maxBufferSize);
|
||||
if (Math.toIntExact((length + offset) / maxBufferSize) == _buffers.length)
|
||||
lastLen = -1;
|
||||
return lastLen;
|
||||
}
|
||||
|
||||
private int calculateEndIndex(long offset, long length)
|
||||
{
|
||||
int endIndex = length < 0 ? (_buffers.length - 1) : Math.toIntExact((length + offset) / maxBufferSize);
|
||||
if (endIndex == _buffers.length)
|
||||
endIndex--;
|
||||
return endIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBytesOccupied()
|
||||
{
|
||||
return _bytesOccupied;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,13 +14,14 @@
|
|||
package org.eclipse.jetty.http.content;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Instant;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jetty.http.CompressedContentFormat;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.MimeTypes.Type;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
|
||||
/**
|
||||
|
@ -61,15 +62,16 @@ public interface HttpContent
|
|||
|
||||
String getETagValue();
|
||||
|
||||
/**
|
||||
* Get the {@link Resource} backing this HTTP content.
|
||||
* @return the backing resource.
|
||||
*/
|
||||
Resource getResource();
|
||||
|
||||
/**
|
||||
* <p>Get this HTTP content as a {@link ByteBuffer} if possible.</p>
|
||||
* <p>Each invocation returns a new {@link ByteBuffer} instance that is
|
||||
* read-only and contains valid data between the pos and the limit.</p>
|
||||
* @return a {@link ByteBuffer} instance or null.
|
||||
* <p>Write a subset of this HTTP content, to a {@link Content.Sink}.</p>
|
||||
*/
|
||||
ByteBuffer getByteBuffer();
|
||||
void writeTo(Content.Sink sink, long offset, long length, Callback callback);
|
||||
|
||||
default long getBytesOccupied()
|
||||
{
|
||||
|
@ -93,7 +95,7 @@ public interface HttpContent
|
|||
HttpContent getContent(String path) throws IOException;
|
||||
}
|
||||
|
||||
// TODO add a writeTo semantic, then update IOResources to use a RBB.Dynamic
|
||||
// TODO update IOResources to use a RBB.Dynamic
|
||||
|
||||
/**
|
||||
* HttpContent Wrapper.
|
||||
|
@ -197,9 +199,9 @@ public interface HttpContent
|
|||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer getByteBuffer()
|
||||
public void writeTo(Content.Sink sink, long offset, long length, Callback callback)
|
||||
{
|
||||
return _delegate.getByteBuffer();
|
||||
_delegate.writeTo(sink, offset, length, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
package org.eclipse.jetty.http.content;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Instant;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -22,6 +21,8 @@ import org.eclipse.jetty.http.EtagUtils;
|
|||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.MimeTypes.Type;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
|
||||
public class PreCompressedHttpContent implements HttpContent
|
||||
|
@ -142,9 +143,9 @@ public class PreCompressedHttpContent implements HttpContent
|
|||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer getByteBuffer()
|
||||
public void writeTo(Content.Sink sink, long offset, long length, Callback callback)
|
||||
{
|
||||
return _precompressedContent.getByteBuffer();
|
||||
_precompressedContent.writeTo(sink, offset, length, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
package org.eclipse.jetty.http.content;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.Set;
|
||||
|
@ -25,6 +24,10 @@ import org.eclipse.jetty.http.HttpField;
|
|||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.http.MimeTypes.Type;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.io.IOResources;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
|
||||
/**
|
||||
|
@ -39,13 +42,15 @@ public class ResourceHttpContent implements HttpContent
|
|||
final Path _path;
|
||||
final String _contentType;
|
||||
final HttpField _etag;
|
||||
final ByteBufferPool.Sized _sizedBufferPool;
|
||||
|
||||
public ResourceHttpContent(final Resource resource, final String contentType)
|
||||
public ResourceHttpContent(Resource resource, String contentType, ByteBufferPool.Sized sizedByteBufferPool)
|
||||
{
|
||||
_resource = resource;
|
||||
_path = resource.getPath();
|
||||
_contentType = contentType;
|
||||
_etag = EtagUtils.createWeakEtagField(resource);
|
||||
_sizedBufferPool = sizedByteBufferPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -144,9 +149,9 @@ public class ResourceHttpContent implements HttpContent
|
|||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer getByteBuffer()
|
||||
public void writeTo(Content.Sink sink, long offset, long length, Callback callback)
|
||||
{
|
||||
return null;
|
||||
IOResources.copy(_resource, sink, _sizedBufferPool, _sizedBufferPool.getSize(), _sizedBufferPool.isDirect(), offset, length, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -18,6 +18,7 @@ import java.nio.file.InvalidPathException;
|
|||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
import org.eclipse.jetty.util.resource.Resources;
|
||||
|
@ -31,12 +32,14 @@ public class ResourceHttpContentFactory implements HttpContent.Factory
|
|||
{
|
||||
private final Resource _baseResource;
|
||||
private final MimeTypes _mimeTypes;
|
||||
private final ByteBufferPool.Sized _sizedBufferPool;
|
||||
|
||||
public ResourceHttpContentFactory(Resource baseResource, MimeTypes mimeTypes)
|
||||
public ResourceHttpContentFactory(Resource baseResource, MimeTypes mimeTypes, ByteBufferPool.Sized sizedBufferPool)
|
||||
{
|
||||
Objects.requireNonNull(mimeTypes, "MimeTypes cannot be null");
|
||||
_baseResource = Objects.requireNonNullElse(baseResource, ResourceFactory.root().newResource("."));
|
||||
_mimeTypes = mimeTypes;
|
||||
_sizedBufferPool = sizedBufferPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -73,7 +76,7 @@ public class ResourceHttpContentFactory implements HttpContent.Factory
|
|||
{
|
||||
if (resource == null || !resource.exists())
|
||||
return null;
|
||||
return new ResourceHttpContent(resource, _mimeTypes.getMimeByExtension(pathInContext));
|
||||
return new ResourceHttpContent(resource, _mimeTypes.getMimeByExtension(pathInContext), _sizedBufferPool);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -15,6 +15,7 @@ package org.eclipse.jetty.http.content;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -32,13 +33,15 @@ public class VirtualHttpContentFactory implements HttpContent.Factory
|
|||
private final Resource _resource;
|
||||
private final String _contentType;
|
||||
private final String _matchSuffix;
|
||||
private final ByteBufferPool.Sized _sizedBufferPool;
|
||||
|
||||
public VirtualHttpContentFactory(HttpContent.Factory factory, Resource resource, String contentType)
|
||||
public VirtualHttpContentFactory(HttpContent.Factory factory, Resource resource, String contentType, ByteBufferPool.Sized sizedBufferPool)
|
||||
{
|
||||
_factory = factory;
|
||||
_resource = resource;
|
||||
_matchSuffix = "/" + _resource.getFileName();
|
||||
_contentType = contentType;
|
||||
_sizedBufferPool = sizedBufferPool;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("resource=({}) {}, resource.getFileName()={}", _resource.getClass().getName(), _resource, _resource.getFileName());
|
||||
}
|
||||
|
@ -58,7 +61,7 @@ public class VirtualHttpContentFactory implements HttpContent.Factory
|
|||
if (content != null)
|
||||
return content;
|
||||
if (matchResource(path))
|
||||
return new ResourceHttpContent(_resource, _contentType);
|
||||
return new ResourceHttpContent(_resource, _contentType, _sizedBufferPool);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.http.content;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
|
||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
|
||||
import org.eclipse.jetty.util.Blocker;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
@ExtendWith(WorkDirExtension.class)
|
||||
public class FileMappingHttpContentFactoryTest
|
||||
{
|
||||
public WorkDir workDir;
|
||||
|
||||
@Test
|
||||
public void testMultiBufferFileMapped() throws Exception
|
||||
{
|
||||
Path file = Files.writeString(workDir.getEmptyPathDir().resolve("file.txt"), "0123456789abcdefghijABCDEFGHIJ");
|
||||
FileMappingHttpContentFactory fileMappingHttpContentFactory = new FileMappingHttpContentFactory(
|
||||
new ResourceHttpContentFactory(ResourceFactory.root().newResource(file.getParent()), MimeTypes.DEFAULTS, ByteBufferPool.SIZED_NON_POOLING),
|
||||
0, 10);
|
||||
|
||||
HttpContent content = fileMappingHttpContentFactory.getContent("file.txt");
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> writeToString(content, 0, 31));
|
||||
assertThrows(IllegalArgumentException.class, () -> writeToString(content, 30, 1));
|
||||
assertThrows(IllegalArgumentException.class, () -> writeToString(content, 31, 0));
|
||||
|
||||
assertThat(writeToString(content, 0, 30), is("0123456789abcdefghijABCDEFGHIJ"));
|
||||
assertThat(writeToString(content, 29, 1), is("J"));
|
||||
assertThat(writeToString(content, 0, 0), is(""));
|
||||
assertThat(writeToString(content, 10, 0), is(""));
|
||||
assertThat(writeToString(content, 15, 0), is(""));
|
||||
assertThat(writeToString(content, 20, 0), is(""));
|
||||
assertThat(writeToString(content, 30, 0), is(""));
|
||||
assertThat(writeToString(content, 1, 28), is("123456789abcdefghijABCDEFGHI"));
|
||||
|
||||
assertThat(writeToString(content, 0, 10), is("0123456789"));
|
||||
assertThat(writeToString(content, 10, 10), is("abcdefghij"));
|
||||
assertThat(writeToString(content, 20, 10), is("ABCDEFGHIJ"));
|
||||
assertThat(writeToString(content, 5, 10), is("56789abcde"));
|
||||
assertThat(writeToString(content, 15, 10), is("fghijABCDE"));
|
||||
assertThat(writeToString(content, 25, 5), is("FGHIJ"));
|
||||
|
||||
assertThat(writeToString(content, 0, -1), is("0123456789abcdefghijABCDEFGHIJ"));
|
||||
assertThat(writeToString(content, 5, -1), is("56789abcdefghijABCDEFGHIJ"));
|
||||
assertThat(writeToString(content, 10, -1), is("abcdefghijABCDEFGHIJ"));
|
||||
assertThat(writeToString(content, 15, -1), is("fghijABCDEFGHIJ"));
|
||||
assertThat(writeToString(content, 20, -1), is("ABCDEFGHIJ"));
|
||||
assertThat(writeToString(content, 25, -1), is("FGHIJ"));
|
||||
}
|
||||
|
||||
private static String writeToString(HttpContent content, long offset, long length) throws IOException
|
||||
{
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (Blocker.Callback cb = Blocker.callback())
|
||||
{
|
||||
content.writeTo(Content.Sink.from(baos), offset, length, cb);
|
||||
cb.block();
|
||||
}
|
||||
return baos.toString(StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
|
@ -267,29 +267,7 @@ public class Content
|
|||
*/
|
||||
static Content.Source from(ByteBufferPool.Sized byteBufferPool, InputStream inputStream, long offset, long length)
|
||||
{
|
||||
return new InputStreamContentSource(inputStream, byteBufferPool)
|
||||
{
|
||||
private long skip = offset;
|
||||
private long toRead = length;
|
||||
|
||||
@Override
|
||||
protected int fillBufferFromInputStream(InputStream inputStream, byte[] buffer) throws IOException
|
||||
{
|
||||
if (skip > 0)
|
||||
{
|
||||
inputStream.skipNBytes(skip);
|
||||
skip = 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
return new InputStreamContentSource(inputStream, byteBufferPool, offset, length);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.eclipse.jetty.util.resource.Resource;
|
|||
|
||||
/**
|
||||
* Common IO operations for {@link Resource} content.
|
||||
* // TODO use ByteBufferPool.Sized instead of ByteBufferPool in all signatures.
|
||||
*/
|
||||
public class IOResources
|
||||
{
|
||||
|
@ -177,12 +178,12 @@ public class IOResources
|
|||
* @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 offset 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
|
||||
public static Content.Source asContentSource(Resource resource, ByteBufferPool bufferPool, int bufferSize, boolean direct, long offset, long length) throws IllegalArgumentException
|
||||
{
|
||||
if (resource.isDirectory() || !resource.exists())
|
||||
throw new IllegalArgumentException("Resource must exist and cannot be a directory: " + resource);
|
||||
|
@ -190,9 +191,7 @@ public class IOResources
|
|||
// 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 Content.Source.from(new ByteBufferPool.Sized(bufferPool, direct, bufferSize), path, first, length);
|
||||
}
|
||||
return Content.Source.from(new ByteBufferPool.Sized(bufferPool, direct, bufferSize), path, offset, length);
|
||||
|
||||
// Try an optimization for MemoryResource.
|
||||
if (resource instanceof MemoryResource memoryResource)
|
||||
|
@ -204,7 +203,7 @@ public class IOResources
|
|||
InputStream inputStream = resource.newInputStream();
|
||||
if (inputStream == null)
|
||||
throw new IllegalArgumentException("Resource does not support InputStream: " + resource);
|
||||
return Content.Source.from(new ByteBufferPool.Sized(bufferPool, direct, bufferSize), inputStream, first, length);
|
||||
return Content.Source.from(new ByteBufferPool.Sized(bufferPool, direct, bufferSize), inputStream, offset, length);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
|
@ -254,35 +253,35 @@ public class IOResources
|
|||
*/
|
||||
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
|
||||
{
|
||||
try
|
||||
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)
|
||||
{
|
||||
new PathToSinkCopier(path, sink, bufferPool, bufferSize, direct, callback).iterate();
|
||||
return;
|
||||
}
|
||||
catch (Throwable x)
|
||||
|
||||
// Directly write the byte array if the resource is a MemoryResource.
|
||||
if (resource instanceof MemoryResource memoryResource)
|
||||
{
|
||||
callback.failed(x);
|
||||
byte[] bytes = memoryResource.getBytes();
|
||||
sink.write(true, ByteBuffer.wrap(bytes), callback);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Directly write the byte array if the resource is a MemoryResource.
|
||||
if (resource instanceof MemoryResource memoryResource)
|
||||
// Fallback to Content.Source.
|
||||
Content.Source source = asContentSource(resource, bufferPool, bufferSize, direct);
|
||||
Content.copy(source, sink, callback);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
byte[] bytes = memoryResource.getBytes();
|
||||
sink.write(true, ByteBuffer.wrap(bytes), callback);
|
||||
return;
|
||||
callback.failed(x);
|
||||
}
|
||||
|
||||
// Fallback to Content.Source.
|
||||
Content.Source source = asContentSource(resource, bufferPool, bufferSize, direct);
|
||||
Content.copy(source, sink, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -304,40 +303,35 @@ public class IOResources
|
|||
*/
|
||||
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
|
||||
{
|
||||
try
|
||||
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)
|
||||
{
|
||||
new PathToSinkCopier(path, sink, bufferPool, bufferSize, direct, first, length, callback).iterate();
|
||||
return;
|
||||
}
|
||||
catch (Throwable x)
|
||||
|
||||
// Directly write the byte array if the resource is a MemoryResource.
|
||||
if (resource instanceof MemoryResource memoryResource)
|
||||
{
|
||||
callback.failed(x);
|
||||
ByteBuffer byteBuffer = BufferUtil.slice(ByteBuffer.wrap(memoryResource.getBytes()), Math.toIntExact(first), Math.toIntExact(length));
|
||||
sink.write(true, byteBuffer, callback);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Directly write the byte array if the resource is a MemoryResource.
|
||||
if (resource instanceof MemoryResource memoryResource)
|
||||
// Fallback to Content.Source.
|
||||
Content.Source source = asContentSource(resource, bufferPool, bufferSize, direct, first, length);
|
||||
Content.copy(source, sink, callback);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
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;
|
||||
callback.failed(x);
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -356,12 +350,16 @@ public class IOResources
|
|||
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
|
||||
public PathToSinkCopier(Path path, Content.Sink sink, ByteBufferPool pool, int bufferSize, boolean direct, long offset, long length, Callback callback) throws IOException
|
||||
{
|
||||
super(callback);
|
||||
this.channel = Files.newByteChannel(path);
|
||||
if (first > -1)
|
||||
channel.position(first);
|
||||
if (offset > -1)
|
||||
{
|
||||
if (offset > channel.size() && length != 0)
|
||||
throw new IllegalArgumentException("Offset outside of Path range");
|
||||
channel.position(offset);
|
||||
}
|
||||
this.sink = sink;
|
||||
this.pool = pool == null ? ByteBufferPool.NON_POOLING : pool;
|
||||
this.bufferSize = bufferSize <= 0 ? 4096 : bufferSize;
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.util.Objects;
|
|||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.io.RetainableByteBuffer;
|
||||
import org.eclipse.jetty.io.RuntimeIOException;
|
||||
import org.eclipse.jetty.util.ExceptionUtil;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
|
@ -40,65 +41,55 @@ public class InputStreamContentSource implements Content.Source
|
|||
private final AutoLock lock = new AutoLock();
|
||||
private final SerializedInvoker invoker = new SerializedInvoker();
|
||||
private final InputStream inputStream;
|
||||
private ByteBufferPool.Sized bufferPool;
|
||||
private final ByteBufferPool.Sized bufferPool;
|
||||
private Runnable demandCallback;
|
||||
private Content.Chunk errorChunk;
|
||||
private long toRead;
|
||||
private boolean closed;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #InputStreamContentSource(InputStream, ByteBufferPool.Sized)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public InputStreamContentSource(InputStream inputStream)
|
||||
{
|
||||
this(inputStream, null);
|
||||
}
|
||||
|
||||
public InputStreamContentSource(InputStream inputStream, ByteBufferPool bufferPool)
|
||||
{
|
||||
this(inputStream, bufferPool instanceof ByteBufferPool.Sized sized ? sized : new ByteBufferPool.Sized(bufferPool));
|
||||
}
|
||||
|
||||
public InputStreamContentSource(InputStream inputStream, ByteBufferPool.Sized bufferPool)
|
||||
{
|
||||
this(inputStream, bufferPool, 0L, -1L);
|
||||
}
|
||||
|
||||
public InputStreamContentSource(InputStream inputStream, ByteBufferPool.Sized bufferPool, long offset, long length)
|
||||
{
|
||||
this.inputStream = Objects.requireNonNull(inputStream);
|
||||
this.bufferPool = Objects.requireNonNullElse(bufferPool, ByteBufferPool.SIZED_NON_POOLING);
|
||||
bufferPool = Objects.requireNonNullElse(bufferPool, ByteBufferPool.SIZED_NON_POOLING);
|
||||
// Make sure direct is always false as the implementation requires heap buffers to be able to call array().
|
||||
if (bufferPool.isDirect())
|
||||
bufferPool = new ByteBufferPool.Sized(bufferPool.getWrapped(), false, bufferPool.getSize());
|
||||
this.bufferPool = bufferPool;
|
||||
skipToOffset(inputStream, offset, length);
|
||||
this.toRead = length;
|
||||
}
|
||||
|
||||
public int getBufferSize()
|
||||
private static void skipToOffset(InputStream inputStream, long offset, long length)
|
||||
{
|
||||
return bufferPool.getSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bufferSize The size of the buffer
|
||||
* @deprecated Use {@link InputStreamContentSource#InputStreamContentSource(InputStream, ByteBufferPool.Sized)}
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public void setBufferSize(int bufferSize)
|
||||
{
|
||||
try (AutoLock ignored = lock.lock())
|
||||
if (offset > 0L && length != 0L)
|
||||
{
|
||||
if (bufferSize != bufferPool.getSize())
|
||||
bufferPool = new ByteBufferPool.Sized(bufferPool.getWrapped(), bufferPool.isDirect(), bufferSize);
|
||||
try
|
||||
{
|
||||
inputStream.skip(offset - 1);
|
||||
if (inputStream.read() == -1)
|
||||
throw new IllegalArgumentException("Offset out of range");
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new RuntimeIOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isUseDirectByteBuffers()
|
||||
{
|
||||
return bufferPool.isDirect();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param useDirectByteBuffers {@code true} if direct buffers will be used.
|
||||
* @deprecated Use {@link InputStreamContentSource#InputStreamContentSource(InputStream, ByteBufferPool.Sized)}
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "12.0.11")
|
||||
public void setUseDirectByteBuffers(boolean useDirectByteBuffers)
|
||||
{
|
||||
try (AutoLock ignored = lock.lock())
|
||||
{
|
||||
if (useDirectByteBuffers != bufferPool.isDirect())
|
||||
bufferPool = new ByteBufferPool.Sized(bufferPool.getWrapped(), useDirectByteBuffers, bufferPool.getSize());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Content.Chunk read()
|
||||
{
|
||||
|
@ -136,7 +127,13 @@ public class InputStreamContentSource implements Content.Source
|
|||
|
||||
protected int fillBufferFromInputStream(InputStream inputStream, byte[] buffer) throws IOException
|
||||
{
|
||||
return inputStream.read(buffer, 0, buffer.length);
|
||||
if (toRead == 0L)
|
||||
return -1;
|
||||
int toReadInt = toRead >= Integer.MAX_VALUE || toRead < 0L ? -1 : (int)toRead;
|
||||
int len = toReadInt > -1 ? Math.min(toReadInt, buffer.length) : buffer.length;
|
||||
int read = inputStream.read(buffer, 0, len);
|
||||
toRead -= read;
|
||||
return read;
|
||||
}
|
||||
|
||||
private void close()
|
||||
|
|
|
@ -18,6 +18,7 @@ import java.util.List;
|
|||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.util.Blocker;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
|
@ -29,6 +30,7 @@ import org.junit.jupiter.params.provider.MethodSource;
|
|||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class IOResourcesTest
|
||||
{
|
||||
|
@ -179,4 +181,28 @@ public class IOResourcesTest
|
|||
assertThat(sum, is(500L));
|
||||
assertThat(chunks.get(chunks.size() - 1).isLast(), is(true));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("all")
|
||||
public void testOutOfRangeOffset(Resource resource)
|
||||
{
|
||||
TestSink sink = new TestSink();
|
||||
Blocker.Callback callback = Blocker.callback();
|
||||
IOResources.copy(resource, sink, bufferPool, 1, false, Integer.MAX_VALUE, 1, callback);
|
||||
assertThrows(IllegalArgumentException.class, callback::block);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("all")
|
||||
public void testOutOfRangeOffsetWithZeroLength(Resource resource) throws Exception
|
||||
{
|
||||
TestSink sink = new TestSink();
|
||||
Callback.Completable callback = new Callback.Completable();
|
||||
IOResources.copy(resource, sink, bufferPool, 1, false, Integer.MAX_VALUE, 0, callback);
|
||||
callback.get();
|
||||
List<Content.Chunk> chunks = sink.takeAccumulatedChunks();
|
||||
long sum = chunks.stream().mapToLong(Content.Chunk::remaining).sum();
|
||||
assertThat(sum, is(0L));
|
||||
assertThat(chunks.get(chunks.size() - 1).isLast(), is(true));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,6 @@ 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.Content;
|
||||
import org.eclipse.jetty.io.IOResources;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
|
@ -659,7 +658,7 @@ public class ResourceService
|
|||
putHeaders(response, content, USE_KNOWN_CONTENT_LENGTH);
|
||||
else
|
||||
putHeaders(response, content, NO_CONTENT_LENGTH);
|
||||
writeHttpContent(request, response, callback, content);
|
||||
content.writeTo(response, 0L, -1L, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -681,9 +680,7 @@ public class ResourceService
|
|||
putHeaders(response, content, range.getLength());
|
||||
response.setStatus(HttpStatus.PARTIAL_CONTENT_206);
|
||||
response.getHeaders().put(HttpHeader.CONTENT_RANGE, range.toHeaderValue(contentLength));
|
||||
|
||||
// TODO use a buffer pool
|
||||
IOResources.copy(content.getResource(), response, null, 0, false, range.first(), range.getLength(), callback);
|
||||
content.writeTo(response, range.first(), range.getLength(), callback);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -700,32 +697,6 @@ public class ResourceService
|
|||
Content.copy(byteRanges, response, callback);
|
||||
}
|
||||
|
||||
protected void writeHttpContent(Request request, Response response, Callback callback, HttpContent content)
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteBuffer buffer = content.getByteBuffer(); // this buffer is going to be consumed by response.write()
|
||||
if (buffer != null)
|
||||
{
|
||||
response.write(true, buffer, callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
IOResources.copy(
|
||||
content.getResource(),
|
||||
response, request.getComponents().getByteBufferPool(),
|
||||
request.getConnectionMetaData().getHttpConfiguration().getOutputBufferSize(),
|
||||
request.getConnectionMetaData().getHttpConfiguration().isUseOutputDirectByteBuffers(),
|
||||
callback);
|
||||
}
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
content.release();
|
||||
callback.failed(x);
|
||||
}
|
||||
}
|
||||
|
||||
protected void putHeaders(Response response, HttpContent content, long contentLength)
|
||||
{
|
||||
// TODO it is very inefficient to do many put's to a HttpFields, as each put is a full iteration.
|
||||
|
|
|
@ -54,9 +54,11 @@ public class ResourceHandler extends Handler.Wrapper
|
|||
// - request ranges
|
||||
// - a way to configure caching or not
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResourceHandler.class);
|
||||
private static final int DEFAULT_BUFFER_SIZE = 32768;
|
||||
private static final boolean DEFAULT_USE_DIRECT_BUFFERS = true;
|
||||
|
||||
private final ResourceService _resourceService = newResourceService();
|
||||
private ByteBufferPool _byteBufferPool;
|
||||
private ByteBufferPool.Sized _byteBufferPool;
|
||||
private Resource _baseResource;
|
||||
private Resource _styleSheet;
|
||||
private MimeTypes _mimeTypes;
|
||||
|
@ -65,12 +67,18 @@ public class ResourceHandler extends Handler.Wrapper
|
|||
|
||||
public ResourceHandler()
|
||||
{
|
||||
this(null);
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public ResourceHandler(Handler handler)
|
||||
{
|
||||
this(handler, null);
|
||||
}
|
||||
|
||||
public ResourceHandler(Handler handler, ByteBufferPool.Sized byteBufferPool)
|
||||
{
|
||||
super(handler);
|
||||
_byteBufferPool = byteBufferPool;
|
||||
}
|
||||
|
||||
protected ResourceService newResourceService()
|
||||
|
@ -99,9 +107,10 @@ public class ResourceHandler extends Handler.Wrapper
|
|||
|
||||
setMimeTypes(context == null ? MimeTypes.DEFAULTS : context.getMimeTypes());
|
||||
|
||||
_byteBufferPool = getByteBufferPool(context);
|
||||
if (_byteBufferPool == null)
|
||||
_byteBufferPool = new ByteBufferPool.Sized(findByteBufferPool(), DEFAULT_USE_DIRECT_BUFFERS, DEFAULT_BUFFER_SIZE);
|
||||
ResourceService resourceService = getResourceService();
|
||||
resourceService.setHttpContentFactory(newHttpContentFactory());
|
||||
resourceService.setHttpContentFactory(newHttpContentFactory(_byteBufferPool));
|
||||
resourceService.setWelcomeFactory(setupWelcomeFactory());
|
||||
if (getStyleSheet() == null)
|
||||
setStyleSheet(getServer().getDefaultStyleSheet());
|
||||
|
@ -109,10 +118,8 @@ public class ResourceHandler extends Handler.Wrapper
|
|||
super.doStart();
|
||||
}
|
||||
|
||||
private ByteBufferPool getByteBufferPool(Context context)
|
||||
private ByteBufferPool findByteBufferPool()
|
||||
{
|
||||
if (context == null)
|
||||
return ByteBufferPool.NON_POOLING;
|
||||
Server server = getServer();
|
||||
if (server == null)
|
||||
return ByteBufferPool.NON_POOLING;
|
||||
|
@ -124,14 +131,14 @@ public class ResourceHandler extends Handler.Wrapper
|
|||
return _resourceService.getHttpContentFactory();
|
||||
}
|
||||
|
||||
protected HttpContent.Factory newHttpContentFactory()
|
||||
protected HttpContent.Factory newHttpContentFactory(ByteBufferPool.Sized byteBufferPool)
|
||||
{
|
||||
HttpContent.Factory contentFactory = new ResourceHttpContentFactory(getBaseResource(), getMimeTypes());
|
||||
HttpContent.Factory contentFactory = new ResourceHttpContentFactory(getBaseResource(), getMimeTypes(), byteBufferPool);
|
||||
if (isUseFileMapping())
|
||||
contentFactory = new FileMappingHttpContentFactory(contentFactory);
|
||||
contentFactory = new VirtualHttpContentFactory(contentFactory, getStyleSheet(), "text/css");
|
||||
contentFactory = new VirtualHttpContentFactory(contentFactory, getStyleSheet(), "text/css", byteBufferPool);
|
||||
contentFactory = new PreCompressedHttpContentFactory(contentFactory, getPrecompressedFormats());
|
||||
contentFactory = new ValidatingCachingHttpContentFactory(contentFactory, Duration.ofSeconds(1).toMillis(), getByteBufferPool());
|
||||
contentFactory = new ValidatingCachingHttpContentFactory(contentFactory, Duration.ofSeconds(1).toMillis(), byteBufferPool);
|
||||
return contentFactory;
|
||||
}
|
||||
|
||||
|
@ -182,11 +189,6 @@ public class ResourceHandler extends Handler.Wrapper
|
|||
return _baseResource;
|
||||
}
|
||||
|
||||
public ByteBufferPool getByteBufferPool()
|
||||
{
|
||||
return _byteBufferPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cacheControl header to set on all static content..
|
||||
* @return the cacheControl header to set on all static content.
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
package org.eclipse.jetty.server.handler;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
@ -30,7 +29,6 @@ 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;
|
||||
|
@ -104,9 +102,9 @@ public class ResourceHandlerByteRangesTest
|
|||
final Resource memResource = ResourceFactory.of(this).newMemoryResource(getClass().getResource("/simple/big.txt"));
|
||||
|
||||
@Override
|
||||
protected HttpContent.Factory newHttpContentFactory()
|
||||
protected HttpContent.Factory newHttpContentFactory(ByteBufferPool.Sized byteBufferPool)
|
||||
{
|
||||
return path -> new ResourceHttpContent(memResource, "text/plain");
|
||||
return path -> new ResourceHttpContent(memResource, "text/plain", byteBufferPool);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -136,9 +134,9 @@ public class ResourceHandlerByteRangesTest
|
|||
final Resource memResource = ResourceFactory.of(this).newMemoryResource(getClass().getResource("/simple/big.txt"));
|
||||
|
||||
@Override
|
||||
protected HttpContent.Factory newHttpContentFactory()
|
||||
protected HttpContent.Factory newHttpContentFactory(ByteBufferPool.Sized byteBufferPool)
|
||||
{
|
||||
return path -> new ResourceHttpContent(memResource, "text/plain");
|
||||
return path -> new ResourceHttpContent(memResource, "text/plain", byteBufferPool);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -170,18 +168,9 @@ public class ResourceHandlerByteRangesTest
|
|||
final Resource memResource = ResourceFactory.of(this).newMemoryResource(getClass().getResource("/simple/big.txt"));
|
||||
|
||||
@Override
|
||||
protected HttpContent.Factory newHttpContentFactory()
|
||||
protected HttpContent.Factory newHttpContentFactory(ByteBufferPool.Sized byteBufferPool)
|
||||
{
|
||||
return path -> new ResourceHttpContent(memResource, "text/plain")
|
||||
{
|
||||
final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), ByteBufferPool.NON_POOLING, false).getByteBuffer();
|
||||
|
||||
@Override
|
||||
public ByteBuffer getByteBuffer()
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
};
|
||||
return path -> new ResourceHttpContent(memResource, "text/plain", byteBufferPool);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -211,18 +200,9 @@ public class ResourceHandlerByteRangesTest
|
|||
final Resource memResource = ResourceFactory.of(this).newMemoryResource(getClass().getResource("/simple/big.txt"));
|
||||
|
||||
@Override
|
||||
protected HttpContent.Factory newHttpContentFactory()
|
||||
protected HttpContent.Factory newHttpContentFactory(ByteBufferPool.Sized byteBufferPool)
|
||||
{
|
||||
return path -> new ResourceHttpContent(memResource, "text/plain")
|
||||
{
|
||||
final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), ByteBufferPool.NON_POOLING, false).getByteBuffer();
|
||||
|
||||
@Override
|
||||
public ByteBuffer getByteBuffer()
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
};
|
||||
return path -> new ResourceHttpContent(memResource, "text/plain", byteBufferPool);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -254,9 +234,9 @@ public class ResourceHandlerByteRangesTest
|
|||
final Resource memResource = ResourceFactory.of(this).newMemoryResource(getClass().getResource("/simple/big.txt"));
|
||||
|
||||
@Override
|
||||
protected HttpContent.Factory newHttpContentFactory()
|
||||
protected HttpContent.Factory newHttpContentFactory(ByteBufferPool.Sized byteBufferPool)
|
||||
{
|
||||
return path -> new ResourceHttpContent(memResource, "text/plain");
|
||||
return path -> new ResourceHttpContent(memResource, "text/plain", byteBufferPool);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -49,6 +49,7 @@ import org.eclipse.jetty.http.content.PreCompressedHttpContentFactory;
|
|||
import org.eclipse.jetty.http.content.ResourceHttpContentFactory;
|
||||
import org.eclipse.jetty.http.content.ValidatingCachingHttpContentFactory;
|
||||
import org.eclipse.jetty.http.content.VirtualHttpContentFactory;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.logging.StacklessLogging;
|
||||
import org.eclipse.jetty.server.AllowedResourceAliasChecker;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
|
@ -664,13 +665,13 @@ public class ResourceHandlerTest
|
|||
_rootResourceHandler = new ResourceHandler()
|
||||
{
|
||||
@Override
|
||||
protected HttpContent.Factory newHttpContentFactory()
|
||||
protected HttpContent.Factory newHttpContentFactory(ByteBufferPool.Sized byteBufferPool)
|
||||
{
|
||||
HttpContent.Factory contentFactory = new ResourceHttpContentFactory(getBaseResource(), getMimeTypes());
|
||||
HttpContent.Factory contentFactory = new ResourceHttpContentFactory(getBaseResource(), getMimeTypes(), byteBufferPool);
|
||||
contentFactory = new FileMappingHttpContentFactory(contentFactory);
|
||||
contentFactory = new VirtualHttpContentFactory(contentFactory, getStyleSheet(), "text/css");
|
||||
contentFactory = new VirtualHttpContentFactory(contentFactory, getStyleSheet(), "text/css", byteBufferPool);
|
||||
contentFactory = new PreCompressedHttpContentFactory(contentFactory, getPrecompressedFormats());
|
||||
contentFactory = new ValidatingCachingHttpContentFactory(contentFactory, 0, getByteBufferPool());
|
||||
contentFactory = new ValidatingCachingHttpContentFactory(contentFactory, 0, byteBufferPool);
|
||||
return contentFactory;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.eclipse.jetty.http.HttpURI;
|
|||
import org.eclipse.jetty.http.content.HttpContent;
|
||||
import org.eclipse.jetty.http.content.ResourceHttpContentFactory;
|
||||
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
|
@ -100,10 +101,10 @@ public class TryPathsHandlerTest
|
|||
ResourceHandler resourceHandler = new ResourceHandler()
|
||||
{
|
||||
@Override
|
||||
protected HttpContent.Factory newHttpContentFactory()
|
||||
protected HttpContent.Factory newHttpContentFactory(ByteBufferPool.Sized byteBufferPool)
|
||||
{
|
||||
// We don't want to cache not found entries for this test.
|
||||
return new ResourceHttpContentFactory(getBaseResource(), getMimeTypes());
|
||||
return new ResourceHttpContentFactory(getBaseResource(), getMimeTypes(), byteBufferPool);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -171,10 +172,10 @@ public class TryPathsHandlerTest
|
|||
ResourceHandler resourceHandler = new ResourceHandler()
|
||||
{
|
||||
@Override
|
||||
protected HttpContent.Factory newHttpContentFactory()
|
||||
protected HttpContent.Factory newHttpContentFactory(ByteBufferPool.Sized byteBufferPool)
|
||||
{
|
||||
// We don't want to cache not found entries for this test.
|
||||
return new ResourceHttpContentFactory(getBaseResource(), getMimeTypes());
|
||||
return new ResourceHttpContentFactory(getBaseResource(), getMimeTypes(), byteBufferPool);
|
||||
}
|
||||
};
|
||||
resourceHandler.setDirAllowed(false);
|
||||
|
@ -299,10 +300,10 @@ public class TryPathsHandlerTest
|
|||
ResourceHandler resourceHandler = new ResourceHandler()
|
||||
{
|
||||
@Override
|
||||
protected HttpContent.Factory newHttpContentFactory()
|
||||
protected HttpContent.Factory newHttpContentFactory(ByteBufferPool.Sized byteBufferPool)
|
||||
{
|
||||
// We don't want to cache not found entries for this test.
|
||||
return new ResourceHttpContentFactory(getBaseResource(), getMimeTypes());
|
||||
return new ResourceHttpContentFactory(getBaseResource(), getMimeTypes(), byteBufferPool);
|
||||
}
|
||||
};
|
||||
resourceHandler.setDirAllowed(false);
|
||||
|
|
|
@ -287,6 +287,23 @@ public class BufferUtil
|
|||
flipToFlush(buffer, p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Slice a buffer given an offset and a length.
|
||||
* @param buffer the buffer to slice
|
||||
* @param offset the offset
|
||||
* @param length the length, -1 meaning use the current limit
|
||||
* @return the sliced buffer
|
||||
*/
|
||||
public static ByteBuffer slice(ByteBuffer buffer, int offset, int length)
|
||||
{
|
||||
ByteBuffer slice = buffer.slice();
|
||||
if (offset > 0)
|
||||
slice.position(slice.position() + offset);
|
||||
if (length > -1)
|
||||
slice.limit(slice.position() + length);
|
||||
return slice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a ByteBuffer to a byte array.
|
||||
*
|
||||
|
|
|
@ -17,6 +17,7 @@ import java.io.ByteArrayOutputStream;
|
|||
import java.io.IOException;
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
@ -57,6 +58,22 @@ public class BufferUtilTest
|
|||
assertThat(FileSystemPool.INSTANCE.mounts(), empty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSlice()
|
||||
{
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap("0123456789".getBytes(StandardCharsets.UTF_8));
|
||||
assertEquals("0123456789", BufferUtil.toString(BufferUtil.slice(byteBuffer, 0, -1)));
|
||||
assertEquals("3456789", BufferUtil.toString(BufferUtil.slice(byteBuffer, 3, -1)));
|
||||
assertEquals("01234567", BufferUtil.toString(BufferUtil.slice(byteBuffer, 0, 8)));
|
||||
assertEquals("5678", BufferUtil.toString(BufferUtil.slice(byteBuffer, 5, 4)));
|
||||
assertEquals("", BufferUtil.toString(BufferUtil.slice(byteBuffer, 1, 0)));
|
||||
assertEquals("", BufferUtil.toString(BufferUtil.slice(byteBuffer, 10, -1)));
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> BufferUtil.slice(byteBuffer, 0, 11));
|
||||
assertThrows(IllegalArgumentException.class, () -> BufferUtil.slice(byteBuffer, 11, -1));
|
||||
assertThrows(IllegalArgumentException.class, () -> BufferUtil.slice(byteBuffer, 1, 10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToInt() throws Exception
|
||||
{
|
||||
|
|
|
@ -149,9 +149,20 @@ import org.slf4j.LoggerFactory;
|
|||
* Defaults to the {@code Server}'s default stylesheet, {@code jetty-dir.css}.
|
||||
* The path of a custom stylesheet to style the directory listing HTML.
|
||||
* </dd>
|
||||
* <dt>byteBufferSize</dt>
|
||||
* <dd>
|
||||
* The size of the buffers to use to serve static resources.
|
||||
* Defaults to {@code 32 KiB}.
|
||||
* </dd>
|
||||
* <dt>useDirectByteBuffers</dt>
|
||||
* <dd>
|
||||
* Use {@code true} to use direct byte buffers to serve static resources.
|
||||
* Defaults to {@code true}.
|
||||
* </dd>
|
||||
* <dt>useFileMappedBuffer</dt>
|
||||
* <dd>
|
||||
* Use {@code true} to use file mapping to serve static resources.
|
||||
* Use {@code true} to use file mapping to serve static resources instead of
|
||||
* buffers configured with the above two settings.
|
||||
* Defaults to {@code false}.
|
||||
* </dd>
|
||||
* <dt>welcomeServlets</dt>
|
||||
|
@ -214,7 +225,9 @@ public class ResourceServlet extends HttpServlet
|
|||
if (contentFactory == null)
|
||||
{
|
||||
MimeTypes mimeTypes = contextHandler.getMimeTypes();
|
||||
contentFactory = new ResourceHttpContentFactory(baseResource, mimeTypes);
|
||||
ByteBufferPool bufferPool = getByteBufferPool(contextHandler);
|
||||
ByteBufferPool.Sized sizedBufferPool = new ByteBufferPool.Sized(bufferPool, getInitBoolean("useDirectByteBuffers", true), getInitInt("byteBufferSize", 32768));
|
||||
contentFactory = new ResourceHttpContentFactory(baseResource, mimeTypes, sizedBufferPool);
|
||||
|
||||
// Use the servers default stylesheet unless there is one explicitly set by an init param.
|
||||
Resource styleSheet = contextHandler.getServer().getDefaultStyleSheet();
|
||||
|
@ -242,7 +255,7 @@ public class ResourceServlet extends HttpServlet
|
|||
if (getInitBoolean("useFileMappedBuffer", false))
|
||||
contentFactory = new FileMappingHttpContentFactory(contentFactory);
|
||||
|
||||
contentFactory = new VirtualHttpContentFactory(contentFactory, styleSheet, "text/css");
|
||||
contentFactory = new VirtualHttpContentFactory(contentFactory, styleSheet, "text/css", sizedBufferPool);
|
||||
contentFactory = new PreCompressedHttpContentFactory(contentFactory, precompressedFormats);
|
||||
|
||||
int maxCacheSize = getInitInt("maxCacheSize", -2);
|
||||
|
@ -251,7 +264,6 @@ public class ResourceServlet extends HttpServlet
|
|||
long cacheValidationTime = getInitParameter("cacheValidationTime") != null ? Long.parseLong(getInitParameter("cacheValidationTime")) : -2;
|
||||
if (maxCachedFiles != -2 || maxCacheSize != -2 || maxCachedFileSize != -2 || cacheValidationTime != -2)
|
||||
{
|
||||
ByteBufferPool bufferPool = getByteBufferPool(contextHandler);
|
||||
ValidatingCachingHttpContentFactory cached = new ValidatingCachingHttpContentFactory(contentFactory,
|
||||
(cacheValidationTime > -2) ? cacheValidationTime : Duration.ofSeconds(1).toMillis(), bufferPool);
|
||||
contentFactory = cached;
|
||||
|
|
|
@ -121,10 +121,9 @@ public class ServletMultiPartFormData
|
|||
}
|
||||
else
|
||||
{
|
||||
// TODO use the size specified in ByteBufferPool.SIZED_NON_POOLING instead of specifying a 2K buffer size?
|
||||
int bufferSize = connection instanceof AbstractConnection c ? c.getInputBufferSize() : 2048;
|
||||
InputStreamContentSource iscs = new InputStreamContentSource(servletRequest.getInputStream(), byteBufferPool);
|
||||
iscs.setBufferSize(bufferSize);
|
||||
source = iscs;
|
||||
source = new InputStreamContentSource(servletRequest.getInputStream(), new ByteBufferPool.Sized(byteBufferPool, false, bufferSize));
|
||||
}
|
||||
|
||||
MultiPartConfig multiPartConfig = Request.getMultiPartConfig(servletContextRequest, filesDirectory)
|
||||
|
|
|
@ -20,7 +20,6 @@ import java.io.InputStream;
|
|||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
|
@ -54,7 +53,6 @@ import org.eclipse.jetty.http.HttpTester;
|
|||
import org.eclipse.jetty.http.UriCompliance;
|
||||
import org.eclipse.jetty.http.content.ResourceHttpContent;
|
||||
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;
|
||||
|
@ -3508,7 +3506,7 @@ public class DefaultServletTest
|
|||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
DefaultServlet defaultServlet = new DefaultServlet();
|
||||
context.addServlet(new ServletHolder(defaultServlet), "/");
|
||||
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain"));
|
||||
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
|
||||
String rawResponse = connector.getResponse("""
|
||||
GET /context/ HTTP/1.1\r
|
||||
|
@ -3529,16 +3527,7 @@ public class DefaultServletTest
|
|||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
DefaultServlet defaultServlet = new DefaultServlet();
|
||||
context.addServlet(new ServletHolder(defaultServlet), "/");
|
||||
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain")
|
||||
{
|
||||
final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), ByteBufferPool.NON_POOLING, false).getByteBuffer();
|
||||
|
||||
@Override
|
||||
public ByteBuffer getByteBuffer()
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
});
|
||||
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
|
||||
String rawResponse = connector.getResponse("""
|
||||
GET /context/ HTTP/1.1\r
|
||||
|
@ -3559,7 +3548,7 @@ public class DefaultServletTest
|
|||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
DefaultServlet defaultServlet = new DefaultServlet();
|
||||
context.addServlet(new ServletHolder(defaultServlet), "/");
|
||||
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain"));
|
||||
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
|
||||
String rawResponse = connector.getResponse("""
|
||||
GET /context/ HTTP/1.1\r
|
||||
|
@ -3583,16 +3572,7 @@ public class DefaultServletTest
|
|||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
DefaultServlet defaultServlet = new DefaultServlet();
|
||||
context.addServlet(new ServletHolder(defaultServlet), "/");
|
||||
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain")
|
||||
{
|
||||
final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), ByteBufferPool.NON_POOLING, false).getByteBuffer();
|
||||
|
||||
@Override
|
||||
public ByteBuffer getByteBuffer()
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
});
|
||||
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
|
||||
String rawResponse = connector.getResponse("""
|
||||
GET /context/ HTTP/1.1\r
|
||||
|
@ -3616,7 +3596,7 @@ public class DefaultServletTest
|
|||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
DefaultServlet defaultServlet = new DefaultServlet();
|
||||
context.addServlet(new ServletHolder(defaultServlet), "/");
|
||||
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain"));
|
||||
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
defaultServlet.getResourceService().setAcceptRanges(false);
|
||||
|
||||
String rawResponse = connector.getResponse("""
|
||||
|
|
|
@ -21,7 +21,6 @@ import java.io.InputStream;
|
|||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
|
@ -59,7 +58,6 @@ 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;
|
||||
|
@ -3646,7 +3644,7 @@ public class ResourceServletTest
|
|||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
ResourceServlet resourceServlet = new ResourceServlet();
|
||||
context.addServlet(new ServletHolder(resourceServlet), "/");
|
||||
resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain"));
|
||||
resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
|
||||
String rawResponse = connector.getResponse("""
|
||||
GET /context/ HTTP/1.1\r
|
||||
|
@ -3667,16 +3665,7 @@ public class ResourceServletTest
|
|||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
ResourceServlet resourceServlet = new ResourceServlet();
|
||||
context.addServlet(new ServletHolder(resourceServlet), "/");
|
||||
resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain")
|
||||
{
|
||||
final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), ByteBufferPool.NON_POOLING, false).getByteBuffer();
|
||||
|
||||
@Override
|
||||
public ByteBuffer getByteBuffer()
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
});
|
||||
resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
|
||||
String rawResponse = connector.getResponse("""
|
||||
GET /context/ HTTP/1.1\r
|
||||
|
@ -3697,7 +3686,7 @@ public class ResourceServletTest
|
|||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
ResourceServlet resourceServlet = new ResourceServlet();
|
||||
context.addServlet(new ServletHolder(resourceServlet), "/");
|
||||
resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain"));
|
||||
resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
|
||||
String rawResponse = connector.getResponse("""
|
||||
GET /context/ HTTP/1.1\r
|
||||
|
@ -3721,16 +3710,7 @@ public class ResourceServletTest
|
|||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
ResourceServlet resourceServlet = new ResourceServlet();
|
||||
context.addServlet(new ServletHolder(resourceServlet), "/");
|
||||
resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain")
|
||||
{
|
||||
final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), ByteBufferPool.NON_POOLING, false).getByteBuffer();
|
||||
|
||||
@Override
|
||||
public ByteBuffer getByteBuffer()
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
});
|
||||
resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
|
||||
String rawResponse = connector.getResponse("""
|
||||
GET /context/ HTTP/1.1\r
|
||||
|
@ -3754,7 +3734,7 @@ public class ResourceServletTest
|
|||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
ResourceServlet resourceServlet = new ResourceServlet();
|
||||
context.addServlet(new ServletHolder(resourceServlet), "/");
|
||||
resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain"));
|
||||
resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
resourceServlet.getResourceService().setAcceptRanges(false);
|
||||
|
||||
String rawResponse = connector.getResponse("""
|
||||
|
|
|
@ -149,9 +149,20 @@ import org.slf4j.LoggerFactory;
|
|||
* Defaults to the {@code Server}'s default stylesheet, {@code jetty-dir.css}.
|
||||
* The path of a custom stylesheet to style the directory listing HTML.
|
||||
* </dd>
|
||||
* <dt>byteBufferSize</dt>
|
||||
* <dd>
|
||||
* The size of the buffers to use to serve static resources.
|
||||
* Defaults to {@code 32 KiB}.
|
||||
* </dd>
|
||||
* <dt>useDirectByteBuffers</dt>
|
||||
* <dd>
|
||||
* Use {@code true} to use direct byte buffers to serve static resources.
|
||||
* Defaults to {@code true}.
|
||||
* </dd>
|
||||
* <dt>useFileMappedBuffer</dt>
|
||||
* <dd>
|
||||
* Use {@code true} to use file mapping to serve static resources.
|
||||
* Use {@code true} to use file mapping to serve static resources instead of
|
||||
* buffers configured with the above two settings.
|
||||
* Defaults to {@code false}.
|
||||
* </dd>
|
||||
* <dt>welcomeServlets</dt>
|
||||
|
@ -214,7 +225,9 @@ public class ResourceServlet extends HttpServlet
|
|||
if (contentFactory == null)
|
||||
{
|
||||
MimeTypes mimeTypes = contextHandler.getMimeTypes();
|
||||
contentFactory = new ResourceHttpContentFactory(baseResource, mimeTypes);
|
||||
ByteBufferPool bufferPool = getByteBufferPool(contextHandler);
|
||||
ByteBufferPool.Sized sizedBufferPool = new ByteBufferPool.Sized(bufferPool, getInitBoolean("useDirectByteBuffers", true), getInitInt("byteBufferSize", 32768));
|
||||
contentFactory = new ResourceHttpContentFactory(baseResource, mimeTypes, sizedBufferPool);
|
||||
|
||||
// Use the servers default stylesheet unless there is one explicitly set by an init param.
|
||||
Resource styleSheet = contextHandler.getServer().getDefaultStyleSheet();
|
||||
|
@ -242,7 +255,7 @@ public class ResourceServlet extends HttpServlet
|
|||
if (getInitBoolean("useFileMappedBuffer", false))
|
||||
contentFactory = new FileMappingHttpContentFactory(contentFactory);
|
||||
|
||||
contentFactory = new VirtualHttpContentFactory(contentFactory, styleSheet, "text/css");
|
||||
contentFactory = new VirtualHttpContentFactory(contentFactory, styleSheet, "text/css", sizedBufferPool);
|
||||
contentFactory = new PreCompressedHttpContentFactory(contentFactory, precompressedFormats);
|
||||
|
||||
int maxCacheSize = getInitInt("maxCacheSize", -2);
|
||||
|
@ -251,7 +264,6 @@ public class ResourceServlet extends HttpServlet
|
|||
long cacheValidationTime = getInitParameter("cacheValidationTime") != null ? Long.parseLong(getInitParameter("cacheValidationTime")) : -2;
|
||||
if (maxCachedFiles != -2 || maxCacheSize != -2 || maxCachedFileSize != -2 || cacheValidationTime != -2)
|
||||
{
|
||||
ByteBufferPool bufferPool = getByteBufferPool(contextHandler);
|
||||
ValidatingCachingHttpContentFactory cached = new ValidatingCachingHttpContentFactory(contentFactory,
|
||||
(cacheValidationTime > -2) ? cacheValidationTime : Duration.ofSeconds(1).toMillis(), bufferPool);
|
||||
contentFactory = cached;
|
||||
|
|
|
@ -125,10 +125,9 @@ public class ServletMultiPartFormData
|
|||
}
|
||||
else
|
||||
{
|
||||
// TODO use the size specified in ByteBufferPool.SIZED_NON_POOLING instead of specifying a 2K buffer size?
|
||||
int bufferSize = connection instanceof AbstractConnection c ? c.getInputBufferSize() : 2048;
|
||||
InputStreamContentSource iscs = new InputStreamContentSource(servletRequest.getInputStream(), byteBufferPool);
|
||||
iscs.setBufferSize(bufferSize);
|
||||
source = iscs;
|
||||
source = new InputStreamContentSource(servletRequest.getInputStream(), new ByteBufferPool.Sized(byteBufferPool, false, bufferSize));
|
||||
}
|
||||
|
||||
parser.setMaxParts(contextHandler.getMaxFormKeys());
|
||||
|
|
|
@ -20,7 +20,6 @@ import java.io.InputStream;
|
|||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
|
@ -54,7 +53,6 @@ import org.eclipse.jetty.http.HttpTester;
|
|||
import org.eclipse.jetty.http.UriCompliance;
|
||||
import org.eclipse.jetty.http.content.ResourceHttpContent;
|
||||
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;
|
||||
|
@ -3508,7 +3506,7 @@ public class DefaultServletTest
|
|||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
DefaultServlet defaultServlet = new DefaultServlet();
|
||||
context.addServlet(new ServletHolder(defaultServlet), "/");
|
||||
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain"));
|
||||
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
|
||||
String rawResponse = connector.getResponse("""
|
||||
GET /context/ HTTP/1.1\r
|
||||
|
@ -3529,16 +3527,7 @@ public class DefaultServletTest
|
|||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
DefaultServlet defaultServlet = new DefaultServlet();
|
||||
context.addServlet(new ServletHolder(defaultServlet), "/");
|
||||
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain")
|
||||
{
|
||||
final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), ByteBufferPool.NON_POOLING, false).getByteBuffer();
|
||||
|
||||
@Override
|
||||
public ByteBuffer getByteBuffer()
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
});
|
||||
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
|
||||
String rawResponse = connector.getResponse("""
|
||||
GET /context/ HTTP/1.1\r
|
||||
|
@ -3559,7 +3548,7 @@ public class DefaultServletTest
|
|||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
DefaultServlet defaultServlet = new DefaultServlet();
|
||||
context.addServlet(new ServletHolder(defaultServlet), "/");
|
||||
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain"));
|
||||
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
|
||||
String rawResponse = connector.getResponse("""
|
||||
GET /context/ HTTP/1.1\r
|
||||
|
@ -3583,16 +3572,7 @@ public class DefaultServletTest
|
|||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
DefaultServlet defaultServlet = new DefaultServlet();
|
||||
context.addServlet(new ServletHolder(defaultServlet), "/");
|
||||
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain")
|
||||
{
|
||||
final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), ByteBufferPool.NON_POOLING, false).getByteBuffer();
|
||||
|
||||
@Override
|
||||
public ByteBuffer getByteBuffer()
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
});
|
||||
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
|
||||
String rawResponse = connector.getResponse("""
|
||||
GET /context/ HTTP/1.1\r
|
||||
|
@ -3616,7 +3596,7 @@ public class DefaultServletTest
|
|||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
DefaultServlet defaultServlet = new DefaultServlet();
|
||||
context.addServlet(new ServletHolder(defaultServlet), "/");
|
||||
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain"));
|
||||
defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
defaultServlet.getResourceService().setAcceptRanges(false);
|
||||
|
||||
String rawResponse = connector.getResponse("""
|
||||
|
|
|
@ -21,7 +21,6 @@ import java.io.InputStream;
|
|||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
|
@ -59,7 +58,6 @@ 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;
|
||||
|
@ -3646,7 +3644,7 @@ public class ResourceServletTest
|
|||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
ResourceServlet resourceServlet = new ResourceServlet();
|
||||
context.addServlet(new ServletHolder(resourceServlet), "/");
|
||||
resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain"));
|
||||
resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
|
||||
String rawResponse = connector.getResponse("""
|
||||
GET /context/ HTTP/1.1\r
|
||||
|
@ -3667,16 +3665,7 @@ public class ResourceServletTest
|
|||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
ResourceServlet resourceServlet = new ResourceServlet();
|
||||
context.addServlet(new ServletHolder(resourceServlet), "/");
|
||||
resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain")
|
||||
{
|
||||
final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), ByteBufferPool.NON_POOLING, false).getByteBuffer();
|
||||
|
||||
@Override
|
||||
public ByteBuffer getByteBuffer()
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
});
|
||||
resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
|
||||
String rawResponse = connector.getResponse("""
|
||||
GET /context/ HTTP/1.1\r
|
||||
|
@ -3697,7 +3686,7 @@ public class ResourceServletTest
|
|||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
ResourceServlet resourceServlet = new ResourceServlet();
|
||||
context.addServlet(new ServletHolder(resourceServlet), "/");
|
||||
resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain"));
|
||||
resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
|
||||
String rawResponse = connector.getResponse("""
|
||||
GET /context/ HTTP/1.1\r
|
||||
|
@ -3721,16 +3710,7 @@ public class ResourceServletTest
|
|||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
ResourceServlet resourceServlet = new ResourceServlet();
|
||||
context.addServlet(new ServletHolder(resourceServlet), "/");
|
||||
resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain")
|
||||
{
|
||||
final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), ByteBufferPool.NON_POOLING, false).getByteBuffer();
|
||||
|
||||
@Override
|
||||
public ByteBuffer getByteBuffer()
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
});
|
||||
resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
|
||||
String rawResponse = connector.getResponse("""
|
||||
GET /context/ HTTP/1.1\r
|
||||
|
@ -3754,7 +3734,7 @@ public class ResourceServletTest
|
|||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
ResourceServlet resourceServlet = new ResourceServlet();
|
||||
context.addServlet(new ServletHolder(resourceServlet), "/");
|
||||
resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain"));
|
||||
resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
resourceServlet.getResourceService().setAcceptRanges(false);
|
||||
|
||||
String rawResponse = connector.getResponse("""
|
||||
|
|
|
@ -1357,12 +1357,40 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("sendContent(http={},{})", httpContent, callback);
|
||||
try
|
||||
{
|
||||
if (prepareSendContent(0, callback))
|
||||
{
|
||||
Content.Sink sink = (last, byteBuffer, cb) ->
|
||||
{
|
||||
_written += byteBuffer.remaining();
|
||||
channelWrite(byteBuffer, last, cb);
|
||||
};
|
||||
httpContent.writeTo(sink, 0L, -1L, new Callback.Nested(callback)
|
||||
{
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
onWriteComplete(true, null);
|
||||
super.succeeded();
|
||||
}
|
||||
|
||||
ByteBuffer buffer = httpContent.getByteBuffer();
|
||||
if (buffer != null)
|
||||
sendContent(buffer, callback);
|
||||
else
|
||||
sendContent(httpContent.getResource(), callback);
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
onWriteComplete(true, x);
|
||||
super.failed(x);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Unable to send http content {}", httpContent, x);
|
||||
_channel.abort(x);
|
||||
callback.failed(x);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean prepareSendContent(int len, Callback callback)
|
||||
|
|
|
@ -52,7 +52,9 @@ public class ResourceHandler extends HandlerWrapper implements WelcomeFactory
|
|||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResourceHandler.class);
|
||||
|
||||
private ByteBufferPool _bufferPool;
|
||||
private ByteBufferPool.Sized _byteBufferPool;
|
||||
private int _byteBufferSize = 32768;
|
||||
private boolean _useDirectByteBuffers = true;
|
||||
Resource _baseResource;
|
||||
ContextHandler _context;
|
||||
Resource _defaultStyleSheet;
|
||||
|
@ -103,7 +105,7 @@ public class ResourceHandler extends HandlerWrapper implements WelcomeFactory
|
|||
if (_mimeTypes == null)
|
||||
_mimeTypes = _context == null ? MimeTypes.DEFAULTS : _context.getMimeTypes();
|
||||
|
||||
_bufferPool = getByteBufferPool(_context);
|
||||
_byteBufferPool = new ByteBufferPool.Sized(getByteBufferPool(_context), _useDirectByteBuffers, _byteBufferSize);
|
||||
if (_resourceService.getHttpContentFactory() == null)
|
||||
_resourceService.setHttpContentFactory(newHttpContentFactory());
|
||||
_resourceService.setWelcomeFactory(this);
|
||||
|
@ -129,11 +131,11 @@ public class ResourceHandler extends HandlerWrapper implements WelcomeFactory
|
|||
protected HttpContent.Factory newHttpContentFactory()
|
||||
{
|
||||
Resource baseResource = getBaseResource();
|
||||
HttpContent.Factory contentFactory = new ResourceHttpContentFactory(baseResource, _mimeTypes);
|
||||
HttpContent.Factory contentFactory = new ResourceHttpContentFactory(baseResource, _mimeTypes, _byteBufferPool);
|
||||
contentFactory = new FileMappingHttpContentFactory(contentFactory);
|
||||
contentFactory = new VirtualHttpContentFactory(contentFactory, getStyleSheet(), "text/css");
|
||||
contentFactory = new VirtualHttpContentFactory(contentFactory, getStyleSheet(), "text/css", _byteBufferPool);
|
||||
contentFactory = new PreCompressedHttpContentFactory(contentFactory, _resourceService.getPrecompressedFormats());
|
||||
contentFactory = new ValidatingCachingHttpContentFactory(contentFactory, Duration.ofSeconds(1).toMillis(), _bufferPool);
|
||||
contentFactory = new ValidatingCachingHttpContentFactory(contentFactory, Duration.ofSeconds(1).toMillis(), _byteBufferPool);
|
||||
return contentFactory;
|
||||
}
|
||||
|
||||
|
@ -282,6 +284,22 @@ public class ResourceHandler extends HandlerWrapper implements WelcomeFactory
|
|||
return _resourceService.isRedirectWelcome();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The size of the byte buffers used to serve static resources.
|
||||
*/
|
||||
public int getByteBufferSize()
|
||||
{
|
||||
return _byteBufferSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether to use direct byte buffers to serve static resources.
|
||||
*/
|
||||
public boolean isUseDirectByteBuffers()
|
||||
{
|
||||
return _useDirectByteBuffers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param acceptRanges If true, range requests and responses are supported
|
||||
*/
|
||||
|
@ -385,6 +403,22 @@ public class ResourceHandler extends HandlerWrapper implements WelcomeFactory
|
|||
_resourceService.setRedirectWelcome(redirectWelcome);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param byteBufferSize The size of the byte buffers used to serve static resources.
|
||||
*/
|
||||
public void setByteBufferSize(int byteBufferSize)
|
||||
{
|
||||
_byteBufferSize = byteBufferSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param useDirectByteBuffers whether to use direct byte buffers to serve static resources.
|
||||
*/
|
||||
public void setUseDirectByteBuffers(boolean useDirectByteBuffers)
|
||||
{
|
||||
_useDirectByteBuffers = useDirectByteBuffers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resourceBase The base resource as a string.
|
||||
* @deprecated use {@link #setBaseResource(Resource)}
|
||||
|
|
|
@ -15,13 +15,9 @@ package org.eclipse.jetty.ee9.nested;
|
|||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
|
@ -36,9 +32,6 @@ import jakarta.servlet.ServletContext;
|
|||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.jetty.ee9.nested.resource.HttpContentRangeWriter;
|
||||
import org.eclipse.jetty.ee9.nested.resource.RangeWriter;
|
||||
import org.eclipse.jetty.ee9.nested.resource.SeekableByteChannelRangeWriter;
|
||||
import org.eclipse.jetty.http.CompressedContentFormat;
|
||||
import org.eclipse.jetty.http.EtagUtils;
|
||||
import org.eclipse.jetty.http.HttpDateTime;
|
||||
|
@ -50,12 +43,12 @@ 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.Content;
|
||||
import org.eclipse.jetty.io.WriterOutputStream;
|
||||
import org.eclipse.jetty.server.ResourceListing;
|
||||
import org.eclipse.jetty.util.Blocker;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.MultiPartOutputStream;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
|
@ -855,15 +848,12 @@ public class ResourceService
|
|||
length += CRLF + DASHDASH + BOUNDARY + DASHDASH + CRLF;
|
||||
response.setContentLengthLong(length);
|
||||
|
||||
try (RangeWriter rangeWriter = HttpContentRangeWriter.newRangeWriter(content))
|
||||
i = 0;
|
||||
for (InclusiveByteRange ibr : ranges)
|
||||
{
|
||||
i = 0;
|
||||
for (InclusiveByteRange ibr : ranges)
|
||||
{
|
||||
multi.startPart(mimetype, new String[]{HttpHeader.CONTENT_RANGE + ": " + header[i]});
|
||||
rangeWriter.writeTo(multi, ibr.getFirst(), ibr.getSize());
|
||||
i++;
|
||||
}
|
||||
multi.startPart(mimetype, new String[]{HttpHeader.CONTENT_RANGE + ": " + header[i]});
|
||||
writeContent(content, multi, ibr.getFirst(), ibr.getSize());
|
||||
i++;
|
||||
}
|
||||
|
||||
multi.close();
|
||||
|
@ -873,37 +863,28 @@ public class ResourceService
|
|||
|
||||
private static void writeContent(HttpContent content, OutputStream out, long start, long contentLength) throws IOException
|
||||
{
|
||||
// attempt efficient ByteBuffer based write
|
||||
ByteBuffer buffer = content.getByteBuffer();
|
||||
if (buffer != null)
|
||||
try (Blocker.Callback blocker = Blocker.callback())
|
||||
{
|
||||
// no need to modify buffer pointers when whole content is requested
|
||||
if (start != 0 || content.getResource().length() != contentLength)
|
||||
// Do not use Content.Sink.from(out) as HttpContent.writeTo() may write a last Chunk
|
||||
// which would then be converted to OutputStream.close(), and we do not want to
|
||||
// close the OutputStream here;
|
||||
// this happens because Content.copy() and IOResources.copy() assume that when they
|
||||
// read a last Chunk from a Content.Source, it should be written as a last Chunk
|
||||
// to the Content.Sink.
|
||||
Content.Sink sink = (last, byteBuffer, callback) ->
|
||||
{
|
||||
buffer = buffer.asReadOnlyBuffer();
|
||||
buffer.position((int)(buffer.position() + start));
|
||||
buffer.limit((int)(buffer.position() + contentLength));
|
||||
}
|
||||
BufferUtil.writeTo(buffer, out);
|
||||
return;
|
||||
}
|
||||
|
||||
// Use a ranged writer if resource backed by path
|
||||
Path path = content.getResource().getPath();
|
||||
if (path != null)
|
||||
{
|
||||
try (SeekableByteChannelRangeWriter rangeWriter = new SeekableByteChannelRangeWriter(() -> Files.newByteChannel(path)))
|
||||
{
|
||||
rangeWriter.writeTo(out, start, contentLength);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform ranged write
|
||||
try (InputStream input = IOResources.asInputStream(content.getResource()))
|
||||
{
|
||||
input.skipNBytes(start);
|
||||
IO.copy(input, out, contentLength);
|
||||
try
|
||||
{
|
||||
BufferUtil.writeTo(byteBuffer, out);
|
||||
callback.succeeded();
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
callback.failed(x);
|
||||
}
|
||||
};
|
||||
content.writeTo(sink, start, contentLength, blocker);
|
||||
blocker.block();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.ee9.nested.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
|
||||
/**
|
||||
* ByteBuffer based RangeWriter
|
||||
*/
|
||||
public class ByteBufferRangeWriter implements RangeWriter
|
||||
{
|
||||
private final ByteBuffer buffer;
|
||||
|
||||
public ByteBufferRangeWriter(ByteBuffer buffer)
|
||||
{
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream, long skipTo, long length) throws IOException
|
||||
{
|
||||
if (skipTo > Integer.MAX_VALUE)
|
||||
{
|
||||
throw new IllegalArgumentException("Unsupported skipTo " + skipTo + " > " + Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
if (length > Integer.MAX_VALUE)
|
||||
{
|
||||
throw new IllegalArgumentException("Unsupported length " + skipTo + " > " + Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
ByteBuffer src = buffer.slice();
|
||||
src.position((int)skipTo);
|
||||
src.limit(Math.addExact((int)skipTo, (int)length));
|
||||
BufferUtil.writeTo(src, outputStream);
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.ee9.nested.resource;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
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
|
||||
*/
|
||||
public class HttpContentRangeWriter
|
||||
{
|
||||
/**
|
||||
* Obtain a new RangeWriter for the supplied HttpContent.
|
||||
*
|
||||
* @param content the HttpContent to base RangeWriter on
|
||||
* @return the RangeWriter best suited for the supplied HttpContent
|
||||
*/
|
||||
public static RangeWriter newRangeWriter(HttpContent content)
|
||||
{
|
||||
Objects.requireNonNull(content, "HttpContent");
|
||||
|
||||
// Try direct buffer
|
||||
ByteBuffer buffer = content.getByteBuffer();
|
||||
if (buffer != null)
|
||||
return new ByteBufferRangeWriter(buffer);
|
||||
|
||||
// Try path's SeekableByteChannel
|
||||
Path path = content.getResource().getPath();
|
||||
if (path != null)
|
||||
return new SeekableByteChannelRangeWriter(() -> Files.newByteChannel(path));
|
||||
|
||||
// Fallback to InputStream
|
||||
return new InputStreamRangeWriter(() -> IOResources.asInputStream(content.getResource()));
|
||||
}
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.ee9.nested.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.eclipse.jetty.util.IO;
|
||||
|
||||
/**
|
||||
* Default Range Writer for InputStream
|
||||
*/
|
||||
public class InputStreamRangeWriter implements RangeWriter
|
||||
{
|
||||
|
||||
public static final int NO_PROGRESS_LIMIT = 3;
|
||||
|
||||
public interface InputStreamSupplier
|
||||
{
|
||||
InputStream newInputStream() throws IOException;
|
||||
}
|
||||
|
||||
private final InputStreamSupplier inputStreamSupplier;
|
||||
private boolean closed = false;
|
||||
private InputStream inputStream;
|
||||
private long pos;
|
||||
|
||||
/**
|
||||
* Create InputStreamRangeWriter
|
||||
*
|
||||
* @param inputStreamSupplier Supplier of the InputStream. If the stream needs to be regenerated, such as the next
|
||||
* requested range being before the current position, then the current InputStream is closed and a new one obtained
|
||||
* from this supplier.
|
||||
*/
|
||||
public InputStreamRangeWriter(InputStreamSupplier inputStreamSupplier)
|
||||
{
|
||||
this.inputStreamSupplier = inputStreamSupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
closed = true;
|
||||
if (inputStream != null)
|
||||
{
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream, long skipTo, long length) throws IOException
|
||||
{
|
||||
if (closed)
|
||||
{
|
||||
throw new IOException("RangeWriter is closed");
|
||||
}
|
||||
|
||||
if (inputStream == null)
|
||||
{
|
||||
inputStream = inputStreamSupplier.newInputStream();
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
if (skipTo < pos)
|
||||
{
|
||||
inputStream.close();
|
||||
inputStream = inputStreamSupplier.newInputStream();
|
||||
pos = 0;
|
||||
}
|
||||
if (pos < skipTo)
|
||||
{
|
||||
long skipSoFar = pos;
|
||||
long actualSkipped;
|
||||
int noProgressLoopLimit = NO_PROGRESS_LIMIT;
|
||||
// loop till we reach desired point, break out on lack of progress.
|
||||
while (noProgressLoopLimit > 0 && skipSoFar < skipTo)
|
||||
{
|
||||
actualSkipped = inputStream.skip(skipTo - skipSoFar);
|
||||
if (actualSkipped == 0)
|
||||
{
|
||||
noProgressLoopLimit--;
|
||||
}
|
||||
else if (actualSkipped > 0)
|
||||
{
|
||||
skipSoFar += actualSkipped;
|
||||
noProgressLoopLimit = NO_PROGRESS_LIMIT;
|
||||
}
|
||||
else
|
||||
{
|
||||
// negative values means the stream was closed or reached EOF
|
||||
// either way, we've hit a state where we can no longer
|
||||
// fulfill the requested range write.
|
||||
throw new IOException("EOF reached before InputStream skip destination");
|
||||
}
|
||||
}
|
||||
|
||||
if (noProgressLoopLimit <= 0)
|
||||
{
|
||||
throw new IOException("No progress made to reach InputStream skip position " + (skipTo - pos));
|
||||
}
|
||||
|
||||
pos = skipTo;
|
||||
}
|
||||
|
||||
// TODO this is very inefficient as copy() allocates a 64K buffer.
|
||||
IO.copy(inputStream, outputStream, length);
|
||||
pos += length;
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.ee9.nested.resource;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Interface for writing sections (ranges) of a single resource (SeekableByteChannel, Resource, etc) to an outputStream.
|
||||
*/
|
||||
public interface RangeWriter extends Closeable
|
||||
{
|
||||
/**
|
||||
* Write the specific range (start, size) to the outputStream.
|
||||
*
|
||||
* @param outputStream the stream to write to
|
||||
* @param skipTo the offset / skip-to / seek-to / position in the resource to start the write from
|
||||
* @param length the size of the section to write
|
||||
*/
|
||||
void writeTo(OutputStream outputStream, long skipTo, long length) throws IOException;
|
||||
}
|
|
@ -1,161 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.ee9.nested.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
|
||||
public class SeekableByteChannelRangeWriter implements RangeWriter
|
||||
{
|
||||
public static final int NO_PROGRESS_LIMIT = 3;
|
||||
|
||||
public interface ChannelSupplier
|
||||
{
|
||||
SeekableByteChannel newSeekableByteChannel() throws IOException;
|
||||
}
|
||||
|
||||
private final ChannelSupplier channelSupplier;
|
||||
private final int bufSize;
|
||||
private final ByteBuffer buffer;
|
||||
private SeekableByteChannel channel;
|
||||
private long pos;
|
||||
private boolean defaultSeekMode = true;
|
||||
|
||||
public SeekableByteChannelRangeWriter(SeekableByteChannelRangeWriter.ChannelSupplier channelSupplier)
|
||||
{
|
||||
this(null, channelSupplier);
|
||||
}
|
||||
|
||||
public SeekableByteChannelRangeWriter(SeekableByteChannel initialChannel, SeekableByteChannelRangeWriter.ChannelSupplier channelSupplier)
|
||||
{
|
||||
this.channel = initialChannel;
|
||||
this.channelSupplier = channelSupplier;
|
||||
this.bufSize = IO.bufferSize;
|
||||
this.buffer = BufferUtil.allocate(this.bufSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
if (this.channel != null)
|
||||
{
|
||||
this.channel.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream, long skipTo, long length) throws IOException
|
||||
{
|
||||
skipTo(skipTo);
|
||||
|
||||
// copy from channel to output stream
|
||||
long readTotal = 0;
|
||||
while (readTotal < length)
|
||||
{
|
||||
BufferUtil.clearToFill(buffer);
|
||||
int size = (int)Math.min(bufSize, length - readTotal);
|
||||
buffer.limit(size);
|
||||
int readLen = channel.read(buffer);
|
||||
BufferUtil.flipToFlush(buffer, 0);
|
||||
BufferUtil.writeTo(buffer, outputStream);
|
||||
readTotal += readLen;
|
||||
pos += readLen;
|
||||
}
|
||||
}
|
||||
|
||||
private void skipTo(long skipTo) throws IOException
|
||||
{
|
||||
if (channel == null)
|
||||
{
|
||||
channel = channelSupplier.newSeekableByteChannel();
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
if (defaultSeekMode)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (channel.position() != skipTo)
|
||||
{
|
||||
channel.position(skipTo);
|
||||
pos = skipTo;
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (UnsupportedOperationException e)
|
||||
{
|
||||
defaultSeekMode = false;
|
||||
fallbackSkipTo(skipTo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback mode
|
||||
fallbackSkipTo(skipTo);
|
||||
}
|
||||
}
|
||||
|
||||
private void fallbackSkipTo(long skipTo) throws IOException
|
||||
{
|
||||
if (skipTo < pos)
|
||||
{
|
||||
channel.close();
|
||||
channel = channelSupplier.newSeekableByteChannel();
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
if (pos < skipTo)
|
||||
{
|
||||
long skipSoFar = pos;
|
||||
long actualSkipped;
|
||||
int noProgressLoopLimit = NO_PROGRESS_LIMIT;
|
||||
// loop till we reach desired point, break out on lack of progress.
|
||||
while (noProgressLoopLimit > 0 && skipSoFar < skipTo)
|
||||
{
|
||||
BufferUtil.clearToFill(buffer);
|
||||
int len = (int)Math.min(bufSize, (skipTo - skipSoFar));
|
||||
buffer.limit(len);
|
||||
actualSkipped = channel.read(buffer);
|
||||
if (actualSkipped == 0)
|
||||
{
|
||||
noProgressLoopLimit--;
|
||||
}
|
||||
else if (actualSkipped > 0)
|
||||
{
|
||||
skipSoFar += actualSkipped;
|
||||
noProgressLoopLimit = NO_PROGRESS_LIMIT;
|
||||
}
|
||||
else
|
||||
{
|
||||
// negative values means the stream was closed or reached EOF
|
||||
// either way, we've hit a state where we can no longer
|
||||
// fulfill the requested range write.
|
||||
throw new IOException("EOF reached before SeekableByteChannel skip destination");
|
||||
}
|
||||
}
|
||||
|
||||
if (noProgressLoopLimit <= 0)
|
||||
{
|
||||
throw new IOException("No progress made to reach SeekableByteChannel skip position " + (skipTo - pos));
|
||||
}
|
||||
|
||||
pos = skipTo;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -111,10 +111,14 @@ import org.slf4j.LoggerFactory;
|
|||
* maxCachedFileSize The maximum size of a file to cache
|
||||
* maxCachedFiles The maximum number of files to cache
|
||||
*
|
||||
* byteBufferSize
|
||||
* The size of the buffers used to serve static content when using NIO connector.
|
||||
* useDirectByteBuffers
|
||||
* Whether to use direct buffers to serve static content when using NIO connector.
|
||||
* useFileMappedBuffer
|
||||
* If set to true, it will use mapped file buffer to serve static content
|
||||
* when using NIO connector. Setting this value to false means that
|
||||
* a direct buffer will be used instead of a mapped file buffer.
|
||||
* buffers sized with the above parameters will be used instead of a mapped file buffer.
|
||||
* This is set to false by default by this class, but may be overridden
|
||||
* by eg webdefault-ee9.xml
|
||||
*
|
||||
|
@ -255,7 +259,9 @@ public class DefaultServlet extends HttpServlet implements WelcomeFactory
|
|||
HttpContent.Factory contentFactory = (HttpContent.Factory)getServletContext().getAttribute(HttpContent.Factory.class.getName());
|
||||
if (contentFactory == null)
|
||||
{
|
||||
contentFactory = new ResourceHttpContentFactory(_baseResource, _mimeTypes)
|
||||
ByteBufferPool bufferPool = getByteBufferPool(_contextHandler);
|
||||
ByteBufferPool.Sized sizedBufferPool = new ByteBufferPool.Sized(bufferPool, getInitBoolean("useDirectByteBuffers", true), getInitInt("byteBufferSize", 32768));
|
||||
contentFactory = new ResourceHttpContentFactory(_baseResource, _mimeTypes, sizedBufferPool)
|
||||
{
|
||||
@Override
|
||||
protected Resource resolve(String pathInContext)
|
||||
|
@ -265,7 +271,7 @@ public class DefaultServlet extends HttpServlet implements WelcomeFactory
|
|||
};
|
||||
if (_useFileMappedBuffer)
|
||||
contentFactory = new FileMappingHttpContentFactory(contentFactory);
|
||||
contentFactory = new VirtualHttpContentFactory(contentFactory, _styleSheet, "text/css");
|
||||
contentFactory = new VirtualHttpContentFactory(contentFactory, _styleSheet, "text/css", sizedBufferPool);
|
||||
contentFactory = new PreCompressedHttpContentFactory(contentFactory, _resourceService.getPrecompressedFormats());
|
||||
|
||||
int maxCacheSize = getInitInt("maxCacheSize", -2);
|
||||
|
@ -274,7 +280,6 @@ public class DefaultServlet extends HttpServlet implements WelcomeFactory
|
|||
long cacheValidationTime = getInitParameter("cacheValidationTime") != null ? Long.parseLong(getInitParameter("cacheValidationTime")) : -2;
|
||||
if (maxCachedFiles != -2 || maxCacheSize != -2 || maxCachedFileSize != -2 || cacheValidationTime != -2)
|
||||
{
|
||||
ByteBufferPool bufferPool = getByteBufferPool(_contextHandler);
|
||||
_cachingContentFactory = new ValidatingCachingHttpContentFactory(contentFactory,
|
||||
(cacheValidationTime > -2) ? cacheValidationTime : Duration.ofSeconds(1).toMillis(), bufferPool);
|
||||
contentFactory = _cachingContentFactory;
|
||||
|
|
|
@ -19,7 +19,6 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
|
@ -51,7 +50,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.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.logging.StacklessLogging;
|
||||
import org.eclipse.jetty.server.AllowedResourceAliasChecker;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
|
@ -2763,7 +2762,7 @@ public class DefaultServletTest
|
|||
{
|
||||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
ResourceService resourceService = new ResourceService();
|
||||
resourceService.setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain"));
|
||||
resourceService.setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
DefaultServlet defaultServlet = new DefaultServlet(resourceService);
|
||||
context.addServlet(new ServletHolder(defaultServlet), "/");
|
||||
});
|
||||
|
@ -2791,7 +2790,7 @@ public class DefaultServletTest
|
|||
{
|
||||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
ResourceService resourceService = new ResourceService();
|
||||
resourceService.setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain"));
|
||||
resourceService.setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
DefaultServlet defaultServlet = new DefaultServlet(resourceService);
|
||||
context.addServlet(new ServletHolder(defaultServlet), "/");
|
||||
});
|
||||
|
@ -2822,16 +2821,7 @@ public class DefaultServletTest
|
|||
{
|
||||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
ResourceService resourceService = new ResourceService();
|
||||
resourceService.setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain")
|
||||
{
|
||||
final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), null, false).getByteBuffer();
|
||||
|
||||
@Override
|
||||
public ByteBuffer getByteBuffer()
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
});
|
||||
resourceService.setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
DefaultServlet defaultServlet = new DefaultServlet(resourceService);
|
||||
context.addServlet(new ServletHolder(defaultServlet), "/");
|
||||
});
|
||||
|
@ -2859,16 +2849,7 @@ public class DefaultServletTest
|
|||
{
|
||||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
ResourceService resourceService = new ResourceService();
|
||||
resourceService.setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain")
|
||||
{
|
||||
final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), null, false).getByteBuffer();
|
||||
|
||||
@Override
|
||||
public ByteBuffer getByteBuffer()
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
});
|
||||
resourceService.setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
DefaultServlet defaultServlet = new DefaultServlet(resourceService);
|
||||
context.addServlet(new ServletHolder(defaultServlet), "/");
|
||||
});
|
||||
|
@ -2899,7 +2880,7 @@ public class DefaultServletTest
|
|||
{
|
||||
Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt"));
|
||||
ResourceService resourceService = new ResourceService();
|
||||
resourceService.setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain"));
|
||||
resourceService.setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain", ByteBufferPool.SIZED_NON_POOLING));
|
||||
resourceService.setAcceptRanges(false);
|
||||
DefaultServlet defaultServlet = new DefaultServlet(resourceService);
|
||||
context.addServlet(new ServletHolder(defaultServlet), "/");
|
||||
|
|
Loading…
Reference in New Issue