From 834db7760518b764074d1257c149e5c37644de69 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Wed, 24 Jul 2024 14:07:31 +0200 Subject: [PATCH] Implement HttpContent.writeTo() async API (#12020) #8790 implement HttpContent.writeTo() async API Signed-off-by: Ludovic Orban --- .../client/InputStreamRequestContent.java | 28 ++- .../content/CachingHttpContentFactory.java | 59 ++++--- .../FileMappingHttpContentFactory.java | 157 +++++++++++++---- .../jetty/http/content/HttpContent.java | 20 ++- .../content/PreCompressedHttpContent.java | 7 +- .../http/content/ResourceHttpContent.java | 13 +- .../content/ResourceHttpContentFactory.java | 7 +- .../content/VirtualHttpContentFactory.java | 7 +- .../FileMappingHttpContentFactoryTest.java | 89 ++++++++++ .../java/org/eclipse/jetty/io/Content.java | 24 +-- .../org/eclipse/jetty/io/IOResources.java | 106 ++++++------ .../io/content/InputStreamContentSource.java | 75 ++++---- .../org/eclipse/jetty/io/IOResourcesTest.java | 26 +++ .../eclipse/jetty/server/ResourceService.java | 33 +--- .../jetty/server/handler/ResourceHandler.java | 34 ++-- .../ResourceHandlerByteRangesTest.java | 40 ++--- .../server/handler/ResourceHandlerTest.java | 9 +- .../server/handler/TryPathsHandlerTest.java | 13 +- .../org/eclipse/jetty/util/BufferUtil.java | 17 ++ .../eclipse/jetty/util/BufferUtilTest.java | 17 ++ .../jetty/ee10/servlet/ResourceServlet.java | 20 ++- .../servlet/ServletMultiPartFormData.java | 5 +- .../ee10/servlet/DefaultServletTest.java | 30 +--- .../ee10/servlet/ResourceServletTest.java | 30 +--- .../jetty/ee11/servlet/ResourceServlet.java | 20 ++- .../servlet/ServletMultiPartFormData.java | 5 +- .../ee11/servlet/DefaultServletTest.java | 30 +--- .../ee11/servlet/ResourceServletTest.java | 30 +--- .../eclipse/jetty/ee9/nested/HttpOutput.java | 38 ++++- .../jetty/ee9/nested/ResourceHandler.java | 44 ++++- .../jetty/ee9/nested/ResourceService.java | 73 +++----- .../resource/ByteBufferRangeWriter.java | 57 ------- .../resource/HttpContentRangeWriter.java | 52 ------ .../resource/InputStreamRangeWriter.java | 121 ------------- .../ee9/nested/resource/RangeWriter.java | 33 ---- .../SeekableByteChannelRangeWriter.java | 161 ------------------ .../jetty/ee9/servlet/DefaultServlet.java | 13 +- .../jetty/ee9/servlet/DefaultServletTest.java | 31 +--- 38 files changed, 659 insertions(+), 915 deletions(-) create mode 100644 jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/content/FileMappingHttpContentFactoryTest.java delete mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/ByteBufferRangeWriter.java delete mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/HttpContentRangeWriter.java delete mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/InputStreamRangeWriter.java delete mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/RangeWriter.java delete mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/SeekableByteChannelRangeWriter.java diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/InputStreamRequestContent.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/InputStreamRequestContent.java index 82875fcfc055..355083b3f683 100644 --- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/InputStreamRequestContent.java +++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/InputStreamRequestContent.java @@ -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; diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/CachingHttpContentFactory.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/CachingHttpContentFactory.java index 15a5402e62ed..f2c6e05c7799 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/CachingHttpContentFactory.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/CachingHttpContentFactory.java @@ -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.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 CachedHttpContent(String key, HttpContent httpContent) 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 long getContentLengthValue() return _contentLengthValue; } - @Override - public ByteBuffer getByteBuffer() - { - return _buffer == null ? null : _buffer.getByteBuffer().asReadOnlyBuffer(); - } - @Override public long getBytesOccupied() { @@ -403,6 +388,20 @@ public String getKey() 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 Resource getResource() } @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 diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/FileMappingHttpContentFactory.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/FileMappingHttpContentFactory.java index 34e4f9a62e42..5e31d69120f9 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/FileMappingHttpContentFactory.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/FileMappingHttpContentFactory.java @@ -15,10 +15,13 @@ 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 FileMappingHttpContentFactory(HttpContent.Factory 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 HttpContent getContent(String path) throws IOException 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 AutoLock _lock = new AutoLock(); - private final HttpContent _content; - private volatile ByteBuffer _buffer; + private final 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 + { + sink.write(true, BufferUtil.slice(_buffer, (int)offset, (int)length), callback); + } + catch (Throwable x) { - if (_buffer == null) - _buffer = getMappedByteBuffer(); - return (_buffer == SENTINEL_BUFFER) ? super.getByteBuffer() : _buffer.asReadOnlyBuffer(); + callback.failed(x); } } @Override public long getBytesOccupied() { - ByteBuffer buffer = _buffer; - if (buffer != null) - return (buffer == SENTINEL_BUFFER) ? super.getBytesOccupied() : 0; + return _buffer.remaining(); + } + } + + 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()); - try (AutoLock lock = _lock.lock()) + 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; + 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"); + + 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 t) + catch (Throwable x) { - if (LOG.isDebugEnabled()) - LOG.debug("Error getting Mapped Buffer", t); + 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; + } - return SENTINEL_BUFFER; + @Override + public long getBytesOccupied() + { + return _bytesOccupied; } } } diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/HttpContent.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/HttpContent.java index 8205036ec9bb..4920de35769a 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/HttpContent.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/HttpContent.java @@ -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(); /** - *

Get this HTTP content as a {@link ByteBuffer} if possible.

- *

Each invocation returns a new {@link ByteBuffer} instance that is - * read-only and contains valid data between the pos and the limit.

- * @return a {@link ByteBuffer} instance or null. + *

Write a subset of this HTTP content, to a {@link Content.Sink}.

*/ - ByteBuffer getByteBuffer(); + void writeTo(Content.Sink sink, long offset, long length, Callback callback); default long getBytesOccupied() { @@ -93,7 +95,7 @@ interface Factory 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 Resource getResource() } @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 diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/PreCompressedHttpContent.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/PreCompressedHttpContent.java index e4b053c655e9..d5e1d921bfa5 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/PreCompressedHttpContent.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/PreCompressedHttpContent.java @@ -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.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 String toString() } @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 diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/ResourceHttpContent.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/ResourceHttpContent.java index d6d986228a07..8e0d4e2234ee 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/ResourceHttpContent.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/ResourceHttpContent.java @@ -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.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 String toString() } @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 diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/ResourceHttpContentFactory.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/ResourceHttpContentFactory.java index a4f21aeb8ef3..7cc9490997f3 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/ResourceHttpContentFactory.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/ResourceHttpContentFactory.java @@ -18,6 +18,7 @@ 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 @@ private HttpContent load(String pathInContext, Resource resource) { if (resource == null || !resource.exists()) return null; - return new ResourceHttpContent(resource, _mimeTypes.getMimeByExtension(pathInContext)); + return new ResourceHttpContent(resource, _mimeTypes.getMimeByExtension(pathInContext), _sizedBufferPool); } @Override diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/VirtualHttpContentFactory.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/VirtualHttpContentFactory.java index a82dca4e6522..941ce6d4df27 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/VirtualHttpContentFactory.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/VirtualHttpContentFactory.java @@ -15,6 +15,7 @@ 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 HttpContent getContent(String path) throws IOException if (content != null) return content; if (matchResource(path)) - return new ResourceHttpContent(_resource, _contentType); + return new ResourceHttpContent(_resource, _contentType, _sizedBufferPool); return null; } diff --git a/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/content/FileMappingHttpContentFactoryTest.java b/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/content/FileMappingHttpContentFactoryTest.java new file mode 100644 index 000000000000..bbc52c0e30a5 --- /dev/null +++ b/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/content/FileMappingHttpContentFactoryTest.java @@ -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); + } +} diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Content.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Content.java index da043ee5df0f..e232a39146a2 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Content.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Content.java @@ -267,29 +267,7 @@ static Content.Source from(ByteBufferPool.Sized byteBufferPool, InputStream inpu */ 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); } /** diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/IOResources.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/IOResources.java index 41a59c0330a7..166c91450c48 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/IOResources.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/IOResources.java @@ -31,6 +31,7 @@ /** * 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 static Content.Source asContentSource(Resource resource, ByteBufferPool b * @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 static Content.Source asContentSource(Resource resource, ByteBufferPool b // 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 static Content.Source asContentSource(Resource resource, ByteBufferPool b 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 static InputStream asInputStream(Resource resource) throws IllegalArgumen */ 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 static void copy(Resource resource, Content.Sink sink, ByteBufferPool buf */ 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 PathToSinkCopier(Path path, Content.Sink sink, ByteBufferPool pool, int b 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; diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/InputStreamContentSource.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/InputStreamContentSource.java index b6ea420b8e3f..1007ddde790e 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/InputStreamContentSource.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/InputStreamContentSource.java @@ -21,6 +21,7 @@ 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 = Objects.requireNonNull(inputStream); - this.bufferPool = Objects.requireNonNullElse(bufferPool, ByteBufferPool.SIZED_NON_POOLING); + this(inputStream, bufferPool, 0L, -1L); } - public int getBufferSize() + public InputStreamContentSource(InputStream inputStream, ByteBufferPool.Sized bufferPool, long offset, long length) { - return bufferPool.getSize(); + this.inputStream = Objects.requireNonNull(inputStream); + 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; } - /** - * @param bufferSize The size of the buffer - * @deprecated Use {@link InputStreamContentSource#InputStreamContentSource(InputStream, ByteBufferPool.Sized)} - */ - @Deprecated(forRemoval = true) - public void setBufferSize(int bufferSize) + private static void skipToOffset(InputStream inputStream, long offset, long length) { - 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 Content.Chunk read() 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() diff --git a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/IOResourcesTest.java b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/IOResourcesTest.java index 8bbc13377601..fb0899033dc6 100644 --- a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/IOResourcesTest.java +++ b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/IOResourcesTest.java @@ -18,6 +18,7 @@ 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 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 void testCopyWithFirstAndLength(Resource resource) throws Exception 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 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)); + } } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java index 1dcdf600a9b5..cc9351c9d532 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java @@ -43,7 +43,6 @@ 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 @@ private void sendData(Request request, Response response, Callback callback, Htt 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 @@ private void sendData(Request request, Response response, Callback callback, Htt 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 @@ private void sendData(Request request, Response response, Callback callback, Htt 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. diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java index 80623e346c38..0c61cf365bb3 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java @@ -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 @@ else if (_baseResource.isAlias()) 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 @@ else if (_baseResource.isAlias()) 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 HttpContent.Factory getHttpContentFactory() 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 Resource getBaseResource() 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. diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerByteRangesTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerByteRangesTest.java index 4c11c0a4fa00..b4e84ffe7a63 100644 --- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerByteRangesTest.java +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerByteRangesTest.java @@ -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.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 void testMemoryResourceRange() throws Exception 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 void testMemoryResourceMultipleRanges() throws Exception 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 void testMemoryResourceRangeUsingBufferedHttpContent() throws Exception 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 void testMemoryResourceMultipleRangesUsingBufferedHttpContent() throws Ex 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 void testNotAcceptRanges() throws Exception 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 diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java index 6643cd6fbe0c..10a3bf322e47 100644 --- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java @@ -49,6 +49,7 @@ 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 void before() throws Exception _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; } }; diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/TryPathsHandlerTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/TryPathsHandlerTest.java index ea96b88360d8..74a0cb98861d 100644 --- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/TryPathsHandlerTest.java +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/TryPathsHandlerTest.java @@ -29,6 +29,7 @@ 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 void testTryPaths(WorkDir workDir) throws Exception 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 void testTryPathsWithPathMappings(WorkDir workDir) throws Exception 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 void testTryPathsHandlerAttributes(WorkDir workDir) throws Exception 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); diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java index 14502d18f3dc..475495249608 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java @@ -287,6 +287,23 @@ public static void putIntLittleEndian(ByteBuffer buffer, int value) 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. * diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java index 0e5d2cf44ef6..1d063329ff1d 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java @@ -17,6 +17,7 @@ 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 void afterEach() 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 { diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java index 130e637b13b1..245dca644b5f 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java @@ -149,9 +149,20 @@ * Defaults to the {@code Server}'s default stylesheet, {@code jetty-dir.css}. * The path of a custom stylesheet to style the directory listing HTML. * + *
byteBufferSize
+ *
+ * The size of the buffers to use to serve static resources. + * Defaults to {@code 32 KiB}. + *
+ *
useDirectByteBuffers
+ *
+ * Use {@code true} to use direct byte buffers to serve static resources. + * Defaults to {@code true}. + *
*
useFileMappedBuffer
*
- * 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}. *
*
welcomeServlets
@@ -214,7 +225,9 @@ public void init() throws ServletException 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 void init() throws ServletException 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 void init() throws ServletException 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; diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletMultiPartFormData.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletMultiPartFormData.java index b27b355b2cbb..1aa62fb558fb 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletMultiPartFormData.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletMultiPartFormData.java @@ -121,10 +121,9 @@ public static CompletableFuture from(ServletRequest servletRequest, Strin } 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) diff --git a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/DefaultServletTest.java b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/DefaultServletTest.java index d53f04b9df92..41845f7dfd75 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/DefaultServletTest.java +++ b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/DefaultServletTest.java @@ -20,7 +20,6 @@ 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.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 void testMemoryResourceRange() throws Exception 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 void testMemoryResourceRangeUsingBufferedHttpContent() throws Exception 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 void testMemoryResourceMultipleRanges() throws Exception 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 void testMemoryResourceMultipleRangesUsingBufferedHttpContent() throws Ex 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 void testNotAcceptRanges() throws Exception 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(""" diff --git a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ResourceServletTest.java b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ResourceServletTest.java index 2bd18bced7da..0ee6b8f2bd4d 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ResourceServletTest.java +++ b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ResourceServletTest.java @@ -21,7 +21,6 @@ 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.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 void testMemoryResourceRange() throws Exception 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 void testMemoryResourceRangeUsingBufferedHttpContent() throws Exception 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 void testMemoryResourceMultipleRanges() throws Exception 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 void testMemoryResourceMultipleRangesUsingBufferedHttpContent() throws Ex 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 void testNotAcceptRanges() throws Exception 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(""" diff --git a/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/ResourceServlet.java b/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/ResourceServlet.java index 45b8a6a75925..dad9bd963297 100644 --- a/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/ResourceServlet.java +++ b/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/ResourceServlet.java @@ -149,9 +149,20 @@ * Defaults to the {@code Server}'s default stylesheet, {@code jetty-dir.css}. * The path of a custom stylesheet to style the directory listing HTML. * + *
byteBufferSize
+ *
+ * The size of the buffers to use to serve static resources. + * Defaults to {@code 32 KiB}. + *
+ *
useDirectByteBuffers
+ *
+ * Use {@code true} to use direct byte buffers to serve static resources. + * Defaults to {@code true}. + *
*
useFileMappedBuffer
*
- * 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}. *
*
welcomeServlets
@@ -214,7 +225,9 @@ public void init() throws ServletException 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 void init() throws ServletException 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 void init() throws ServletException 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; diff --git a/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/ServletMultiPartFormData.java b/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/ServletMultiPartFormData.java index 38d0e35d0852..13e4faf2875b 100644 --- a/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/ServletMultiPartFormData.java +++ b/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/ServletMultiPartFormData.java @@ -125,10 +125,9 @@ public static CompletableFuture from(ServletRequest servletRequest, Strin } 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()); diff --git a/jetty-ee11/jetty-ee11-servlet/src/test/java/org/eclipse/jetty/ee11/servlet/DefaultServletTest.java b/jetty-ee11/jetty-ee11-servlet/src/test/java/org/eclipse/jetty/ee11/servlet/DefaultServletTest.java index 35932b8d7e1a..b2618dd5f397 100644 --- a/jetty-ee11/jetty-ee11-servlet/src/test/java/org/eclipse/jetty/ee11/servlet/DefaultServletTest.java +++ b/jetty-ee11/jetty-ee11-servlet/src/test/java/org/eclipse/jetty/ee11/servlet/DefaultServletTest.java @@ -20,7 +20,6 @@ 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.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 void testMemoryResourceRange() throws Exception 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 void testMemoryResourceRangeUsingBufferedHttpContent() throws Exception 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 void testMemoryResourceMultipleRanges() throws Exception 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 void testMemoryResourceMultipleRangesUsingBufferedHttpContent() throws Ex 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 void testNotAcceptRanges() throws Exception 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(""" diff --git a/jetty-ee11/jetty-ee11-servlet/src/test/java/org/eclipse/jetty/ee11/servlet/ResourceServletTest.java b/jetty-ee11/jetty-ee11-servlet/src/test/java/org/eclipse/jetty/ee11/servlet/ResourceServletTest.java index 76b678e8bc55..0100fe971f38 100644 --- a/jetty-ee11/jetty-ee11-servlet/src/test/java/org/eclipse/jetty/ee11/servlet/ResourceServletTest.java +++ b/jetty-ee11/jetty-ee11-servlet/src/test/java/org/eclipse/jetty/ee11/servlet/ResourceServletTest.java @@ -21,7 +21,6 @@ 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.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 void testMemoryResourceRange() throws Exception 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 void testMemoryResourceRangeUsingBufferedHttpContent() throws Exception 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 void testMemoryResourceMultipleRanges() throws Exception 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 void testMemoryResourceMultipleRangesUsingBufferedHttpContent() throws Ex 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 void testNotAcceptRanges() throws Exception 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(""" diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpOutput.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpOutput.java index f85fba691612..45476ecc79ec 100644 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpOutput.java +++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpOutput.java @@ -1357,12 +1357,40 @@ public void sendContent(HttpContent httpContent, Callback callback) { 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) diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ResourceHandler.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ResourceHandler.java index 3e72885f704e..a11f46f201c3 100644 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ResourceHandler.java +++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ResourceHandler.java @@ -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 void doStart() throws Exception 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 HttpContent.Factory getHttpContentFactory() 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 boolean isRedirectWelcome() 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 void setRedirectWelcome(boolean redirectWelcome) _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)} diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ResourceService.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ResourceService.java index 2daf042dc137..b5951295a148 100644 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ResourceService.java +++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ResourceService.java @@ -15,13 +15,9 @@ 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.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.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 String toString() 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 String toString() 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) - { - // no need to modify buffer pointers when whole content is requested - if (start != 0 || content.getResource().length() != contentLength) - { - 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 (Blocker.Callback blocker = Blocker.callback()) { - try (SeekableByteChannelRangeWriter rangeWriter = new SeekableByteChannelRangeWriter(() -> Files.newByteChannel(path))) + // 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) -> { - 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(); } } diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/ByteBufferRangeWriter.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/ByteBufferRangeWriter.java deleted file mode 100644 index 40fb6ace4942..000000000000 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/ByteBufferRangeWriter.java +++ /dev/null @@ -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); - } -} diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/HttpContentRangeWriter.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/HttpContentRangeWriter.java deleted file mode 100644 index 6acefaff35cc..000000000000 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/HttpContentRangeWriter.java +++ /dev/null @@ -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())); - } -} diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/InputStreamRangeWriter.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/InputStreamRangeWriter.java deleted file mode 100644 index 431fbaec8f82..000000000000 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/InputStreamRangeWriter.java +++ /dev/null @@ -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; - } -} diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/RangeWriter.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/RangeWriter.java deleted file mode 100644 index b441245431cf..000000000000 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/RangeWriter.java +++ /dev/null @@ -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; -} diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/SeekableByteChannelRangeWriter.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/SeekableByteChannelRangeWriter.java deleted file mode 100644 index 08384355d881..000000000000 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/SeekableByteChannelRangeWriter.java +++ /dev/null @@ -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; - } - } -} diff --git a/jetty-ee9/jetty-ee9-servlet/src/main/java/org/eclipse/jetty/ee9/servlet/DefaultServlet.java b/jetty-ee9/jetty-ee9-servlet/src/main/java/org/eclipse/jetty/ee9/servlet/DefaultServlet.java index b466d752ca5b..9a46d1896268 100644 --- a/jetty-ee9/jetty-ee9-servlet/src/main/java/org/eclipse/jetty/ee9/servlet/DefaultServlet.java +++ b/jetty-ee9/jetty-ee9-servlet/src/main/java/org/eclipse/jetty/ee9/servlet/DefaultServlet.java @@ -111,10 +111,14 @@ * 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 void init() 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 @@ protected Resource resolve(String pathInContext) }; 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 @@ protected Resource resolve(String pathInContext) 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; diff --git a/jetty-ee9/jetty-ee9-servlet/src/test/java/org/eclipse/jetty/ee9/servlet/DefaultServletTest.java b/jetty-ee9/jetty-ee9-servlet/src/test/java/org/eclipse/jetty/ee9/servlet/DefaultServletTest.java index 880e1dfd0f1d..e07264eafccd 100644 --- a/jetty-ee9/jetty-ee9-servlet/src/test/java/org/eclipse/jetty/ee9/servlet/DefaultServletTest.java +++ b/jetty-ee9/jetty-ee9-servlet/src/test/java/org/eclipse/jetty/ee9/servlet/DefaultServletTest.java @@ -19,7 +19,6 @@ 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.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 void testMemoryResourceRange() throws Exception { 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 void testMemoryResourceMultipleRanges() throws Exception { 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 void testMemoryResourceRangeUsingBufferedHttpContent() throws Exception { 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 void testMemoryResourceMultipleRangesUsingBufferedHttpContent() throws Ex { 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 void testNotAcceptRanges() throws Exception { 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), "/");