From 89c41b2550ed367a25d1664da8843f5a4e1019da Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Thu, 28 Mar 2024 11:08:44 +0100 Subject: [PATCH] Cleanup Resource IO (#11364) Refactorings to rationalize and simplify how we do IO with resources internally by introducing the IOResources helper. Signed-off-by: Ludovic Orban --- .../server/http2/HTTP2ServerDocs.java | 2 +- .../client/NetworkTrafficListenerTest.java | 5 +- .../deploy/providers/ContextProvider.java | 3 +- .../jetty/http/MultiPartByteRanges.java | 32 +- .../content/CachingHttpContentFactory.java | 27 +- .../org/eclipse/jetty/io/IOResources.java | 512 ++++++++++++++++++ .../io/content/InputStreamContentSource.java | 13 +- .../jetty/io/content/PathContentSource.java | 2 +- .../org/eclipse/jetty/io/IOResourcesTest.java | 182 +++++++ .../java/org/eclipse/jetty/io/TestSink.java | 3 +- .../jetty/security/JDBCLoginService.java | 3 +- .../jetty/security/PropertyUserStore.java | 5 +- .../eclipse/jetty/server/ResourceService.java | 85 +-- .../jetty/server/handler/DefaultHandler.java | 3 +- .../server/handler/DebugHandlerTest.java | 3 +- .../ResourceHandlerByteRangesTest.java | 6 +- .../ssl/ServerConnectorSslServerTest.java | 3 +- .../org/eclipse/jetty/util/BufferUtil.java | 3 +- .../jetty/util/resource/MemoryResource.java | 5 + .../jetty/util/resource/PathResource.java | 3 +- .../eclipse/jetty/util/resource/Resource.java | 2 + .../util/resource/FileSystemResourceTest.java | 28 - .../jetty/util/resource/PathResourceTest.java | 21 - .../jetty/util/resource/ResourceTest.java | 4 +- .../util/resource/UrlResourceFactoryTest.java | 18 +- .../ee10/maven/plugin/MavenResource.java | 9 - .../ee10/servlet/ServletContextHandler.java | 3 +- .../ee10/servlet/DefaultServletTest.java | 6 +- .../AbstractWebAppObjectInSessionTest.java | 6 +- .../eclipse/jetty/ee10/webapp/Descriptor.java | 3 +- .../jetty/ee9/maven/plugin/MavenResource.java | 9 - .../jetty/ee9/nested/ContextHandler.java | 3 +- .../eclipse/jetty/ee9/nested/HttpOutput.java | 145 +++-- .../jetty/ee9/nested/ResourceService.java | 3 +- .../resource/HttpContentRangeWriter.java | 3 +- .../resource/InputStreamRangeWriter.java | 1 + .../jetty/ee9/nested/HttpOutputTest.java | 104 ++-- .../jetty/ee9/servlet/DefaultServletTest.java | 6 +- .../AbstractWebAppObjectInSessionTest.java | 6 +- .../eclipse/jetty/ee9/webapp/Descriptor.java | 3 +- 40 files changed, 973 insertions(+), 310 deletions(-) create mode 100644 jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/IOResources.java create mode 100644 jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/IOResourcesTest.java diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http2/HTTP2ServerDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http2/HTTP2ServerDocs.java index a0722bc7eb9..749602bd8d0 100644 --- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http2/HTTP2ServerDocs.java +++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http2/HTTP2ServerDocs.java @@ -334,7 +334,7 @@ public class HTTP2ServerDocs // Send the favicon "response". MetaData.Response pushedResponse = new MetaData.Response(HttpStatus.OK_200, null, HttpVersion.HTTP_2, HttpFields.EMPTY); return pushedStream.headers(new HeadersFrame(pushedStream.getId(), pushedResponse, null, false)) - .thenCompose(pushed -> pushed.data(new DataFrame(pushed.getId(), faviconBuffer, true))); + .thenCompose(pushed -> pushed.data(new DataFrame(pushed.getId(), faviconBuffer.slice(), true))); }); } // Return a Stream.Listener to handle the request events. diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/NetworkTrafficListenerTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/NetworkTrafficListenerTest.java index 7daf294d181..cb9e242ccc5 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/NetworkTrafficListenerTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/NetworkTrafficListenerTest.java @@ -45,6 +45,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.awaitility.Awaitility.await; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -247,8 +248,8 @@ public class NetworkTrafficListenerTest assertTrue(serverIncomingLatch.await(1, TimeUnit.SECONDS)); assertTrue(serverOutgoingLatch.await(1, TimeUnit.SECONDS)); assertTrue(clientIncomingLatch.await(1, TimeUnit.SECONDS)); - assertEquals(clientOutgoing.get(), serverIncoming.get()); - assertEquals(serverOutgoing.get(), clientIncoming.get()); + await().atMost(5, TimeUnit.SECONDS).until(() -> clientOutgoing.get().equals(serverIncoming.get())); + await().atMost(5, TimeUnit.SECONDS).until(() -> serverOutgoing.get().equals(clientIncoming.get())); } @Test diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java index bebd2ab3641..82d905b2155 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java @@ -32,6 +32,7 @@ import java.util.function.Supplier; import java.util.stream.Stream; import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.io.IOResources; import org.eclipse.jetty.server.Deployable; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandler; @@ -149,7 +150,7 @@ public class ContextProvider extends ScanningAppProvider public void loadProperties(Resource resource) throws IOException { Properties props = new Properties(); - try (InputStream inputStream = resource.newInputStream()) + try (InputStream inputStream = IOResources.asInputStream(resource)) { props.load(inputStream); props.forEach((key, value) -> _properties.put((String)key, (String)value)); diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartByteRanges.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartByteRanges.java index 0afe1dfd836..4bea6585684 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartByteRanges.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartByteRanges.java @@ -23,8 +23,9 @@ import java.util.Iterator; import java.util.List; import java.util.concurrent.CompletableFuture; +import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.Content; -import org.eclipse.jetty.io.RuntimeIOException; +import org.eclipse.jetty.io.IOResources; import org.eclipse.jetty.io.content.ContentSourceCompletableFuture; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.thread.AutoLock; @@ -214,36 +215,37 @@ public class MultiPartByteRanges { private final Resource resource; private final ByteRange byteRange; + private final ByteBufferPool bufferPool; public Part(String contentType, Resource resource, ByteRange byteRange, long contentLength) { this(HttpFields.build().put(HttpHeader.CONTENT_TYPE, contentType) - .put(HttpHeader.CONTENT_RANGE, byteRange.toHeaderValue(contentLength)), resource, byteRange); + .put(HttpHeader.CONTENT_RANGE, byteRange.toHeaderValue(contentLength)), resource, byteRange, null); + } + + public Part(String contentType, Resource resource, ByteRange byteRange, long contentLength, ByteBufferPool bufferPool) + { + this(HttpFields.build().put(HttpHeader.CONTENT_TYPE, contentType) + .put(HttpHeader.CONTENT_RANGE, byteRange.toHeaderValue(contentLength)), resource, byteRange, bufferPool); } public Part(HttpFields headers, Resource resource, ByteRange byteRange) + { + this(headers, resource, byteRange, null); + } + + public Part(HttpFields headers, Resource resource, ByteRange byteRange, ByteBufferPool bufferPool) { super(null, null, headers); this.resource = resource; this.byteRange = byteRange; + this.bufferPool = bufferPool == null ? new ByteBufferPool.NonPooling() : bufferPool; } @Override public Content.Source newContentSource() { - // Try using the resource's path if possible, as the nio API is async and helps to avoid buffer copies. - Path path = resource.getPath(); - if (path != null) - return new PathContentSource(path, byteRange); - - try - { - return new InputStreamContentSource(resource.newInputStream(), byteRange); - } - catch (IOException e) - { - throw new RuntimeIOException(e); - } + return IOResources.asContentSource(resource, bufferPool, 0, false, byteRange.first(), byteRange.getLength()); } } 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 f3bed949176..e3fc478d70a 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 @@ -15,7 +15,6 @@ package org.eclipse.jetty.http.content; import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.channels.ReadableByteChannel; import java.time.Instant; import java.util.Set; import java.util.SortedSet; @@ -31,9 +30,9 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.http.PreEncodedHttpField; import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.IOResources; import org.eclipse.jetty.io.Retainable; import org.eclipse.jetty.io.RetainableByteBuffer; -import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.NanoTime; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.resource.Resource; @@ -333,31 +332,21 @@ public class CachingHttpContentFactory implements HttpContent.Factory ByteBuffer byteBuffer = httpContent.getByteBuffer(); if (byteBuffer == null) { - buffer = _bufferPool.acquire((int)_contentLengthValue, _useDirectByteBuffers); try { if (_contentLengthValue <= _maxCachedFileSize) - { - try (ReadableByteChannel readableByteChannel = httpContent.getResource().newReadableByteChannel()) - { - byteBuffer = buffer.getByteBuffer(); - int read = BufferUtil.readFrom(readableByteChannel, byteBuffer); - if (read != _contentLengthValue) - { - buffer.release(); - buffer = null; - isValid = false; - } - } - } + buffer = IOResources.toRetainableByteBuffer(httpContent.getResource(), _bufferPool, _useDirectByteBuffers); + else + buffer = null; } catch (Throwable t) { - if (buffer != null) - buffer.release(); buffer = null; isValid = false; - LOG.warn("Failed to read Resource", t); + if (LOG.isDebugEnabled()) + LOG.warn("Failed to read Resource: {}", httpContent.getResource(), t); + else + LOG.warn("Failed to read Resource: {} - {}", httpContent.getResource(), t.toString()); } } else 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 new file mode 100644 index 00000000000..a5986a7b8df --- /dev/null +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/IOResources.java @@ -0,0 +1,512 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.io; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.eclipse.jetty.io.content.ByteBufferContentSource; +import org.eclipse.jetty.io.content.InputStreamContentSource; +import org.eclipse.jetty.io.content.PathContentSource; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.IteratingNestedCallback; +import org.eclipse.jetty.util.resource.MemoryResource; +import org.eclipse.jetty.util.resource.Resource; + +/** + * Common IO operations for {@link Resource} content. + */ +public class IOResources +{ + /** + *

Reads the contents of a Resource into a RetainableByteBuffer.

+ *

The resource must not be a directory, must exists and there must be + * a way to access its contents.

+ *

Multiple optimized methods are used to access the resource's contents but if they all fail, + * {@link Resource#newInputStream()} is used as a fallback.

+ * + * @param resource the resource to be read. + * @param bufferPool the {@link ByteBufferPool} to get buffers from. null means allocate new buffers as needed. + * @param direct the directness of the buffers. + * @return a {@link RetainableByteBuffer} containing the resource's contents. + * @throws IllegalArgumentException if the resource is a directory or does not exist or there is no way to access its contents. + */ + public static RetainableByteBuffer toRetainableByteBuffer(Resource resource, ByteBufferPool bufferPool, boolean direct) throws IllegalArgumentException + { + if (resource.isDirectory() || !resource.exists()) + throw new IllegalArgumentException("Resource must exist and cannot be a directory: " + resource); + + // Optimize for MemoryResource. + if (resource instanceof MemoryResource memoryResource) + return RetainableByteBuffer.wrap(ByteBuffer.wrap(memoryResource.getBytes())); + + long longLength = resource.length(); + if (longLength > Integer.MAX_VALUE) + throw new IllegalArgumentException("Resource length exceeds 2 GiB: " + resource); + int length = (int)longLength; + + bufferPool = bufferPool == null ? new ByteBufferPool.NonPooling() : bufferPool; + + // Optimize for PathResource. + Path path = resource.getPath(); + if (path != null) + { + RetainableByteBuffer retainableByteBuffer = bufferPool.acquire(length, direct); + try (SeekableByteChannel seekableByteChannel = Files.newByteChannel(path)) + { + long totalRead = 0L; + ByteBuffer byteBuffer = retainableByteBuffer.getByteBuffer(); + int pos = BufferUtil.flipToFill(byteBuffer); + while (totalRead < length) + { + int read = seekableByteChannel.read(byteBuffer); + if (read == -1) + break; + totalRead += read; + } + BufferUtil.flipToFlush(byteBuffer, pos); + return retainableByteBuffer; + } + catch (IOException e) + { + retainableByteBuffer.release(); + throw new RuntimeIOException(e); + } + } + + // Fallback to InputStream. + try (InputStream inputStream = resource.newInputStream()) + { + if (inputStream == null) + throw new IllegalArgumentException("Resource does not support InputStream: " + resource); + + ByteBufferAggregator aggregator = new ByteBufferAggregator(bufferPool, direct, length > -1 ? length : 4096, length > -1 ? length : Integer.MAX_VALUE); + byte[] byteArray = new byte[4096]; + while (true) + { + int read = inputStream.read(byteArray); + if (read == -1) + break; + aggregator.aggregate(ByteBuffer.wrap(byteArray, 0, read)); + } + return aggregator.takeRetainableByteBuffer(); + } + catch (IOException e) + { + throw new RuntimeIOException(e); + } + } + + /** + *

Gets a {@link Content.Source} with the contents of a resource.

+ *

The resource must not be a directory, must exists and there must be + * a way to access its contents.

+ *

Multiple optimized methods are used to access the resource's contents but if they all fail, + * {@link Resource#newInputStream()} is used as a fallback.

+ * + * @param resource the resource from which to get a {@link Content.Source}. + * @param bufferPool the {@link ByteBufferPool} to get buffers from. null means allocate new buffers as needed. + * @param bufferSize the size of the buffer to be used for the copy. Any value < 1 means use a default value. + * @param direct the directness of the buffers, this parameter is ignored if {@code bufferSize} is < 1. + * @return the {@link Content.Source}. + * @throws IllegalArgumentException if the resource is a directory or does not exist or there is no way to access its contents. + */ + public static Content.Source asContentSource(Resource resource, ByteBufferPool bufferPool, int bufferSize, boolean direct) throws IllegalArgumentException + { + if (resource.isDirectory() || !resource.exists()) + throw new IllegalArgumentException("Resource must exist and cannot be a directory: " + resource); + + // Try to find an optimized content source. + Path path = resource.getPath(); + if (path != null) + { + PathContentSource pathContentSource = new PathContentSource(path, bufferPool); + if (bufferSize > 0) + { + pathContentSource.setBufferSize(bufferSize); + pathContentSource.setUseDirectByteBuffers(direct); + } + return pathContentSource; + } + if (resource instanceof MemoryResource memoryResource) + { + byte[] bytes = memoryResource.getBytes(); + return new ByteBufferContentSource(ByteBuffer.wrap(bytes)); + } + + // Fallback to wrapping InputStream. + try + { + return new InputStreamContentSource(resource.newInputStream()); + } + catch (IOException e) + { + throw new RuntimeIOException(e); + } + } + + /** + *

Gets a {@link Content.Source} with a range of the contents of a resource.

+ *

The resource must not be a directory, must exists and there must be + * a way to access its contents.

+ *

Multiple optimized methods are used to access the resource's contents but if they all fail, + * {@link Resource#newInputStream()} is used as a fallback.

+ * + * @param resource the resource from which to get a {@link Content.Source}. + * @param bufferPool the {@link ByteBufferPool} to get buffers from. null means allocate new buffers as needed. + * @param bufferSize the size of the buffer to be used for the copy. Any value < 1 means use a default value. + * @param direct the directness of the buffers, this parameter is ignored if {@code bufferSize} is < 1. + * @param first the first byte from which to read from. + * @param length the length of the content to read. + * @return the {@link Content.Source}. + * @throws IllegalArgumentException if the resource is a directory or does not exist or there is no way to access its contents. + */ + public static Content.Source asContentSource(Resource resource, ByteBufferPool bufferPool, int bufferSize, boolean direct, long first, long length) throws IllegalArgumentException + { + if (resource.isDirectory() || !resource.exists()) + throw new IllegalArgumentException("Resource must exist and cannot be a directory: " + resource); + + // Try using the resource's path if possible, as the nio API is async and helps to avoid buffer copies. + Path path = resource.getPath(); + if (path != null) + { + RangedPathContentSource contentSource = new RangedPathContentSource(path, bufferPool, first, length); + if (bufferSize > 0) + { + contentSource.setBufferSize(bufferSize); + contentSource.setUseDirectByteBuffers(direct); + } + return contentSource; + } + + // Try an optimization for MemoryResource. + if (resource instanceof MemoryResource memoryResource) + return new ByteBufferContentSource(ByteBuffer.wrap(memoryResource.getBytes())); + + // Fallback to InputStream. + try + { + InputStream inputStream = resource.newInputStream(); + if (inputStream == null) + throw new IllegalArgumentException("Resource does not support InputStream: " + resource); + RangedInputStreamContentSource contentSource = new RangedInputStreamContentSource(inputStream, bufferPool, first, length); + if (bufferSize > 0) + { + contentSource.setBufferSize(bufferSize); + contentSource.setUseDirectByteBuffers(direct); + } + return contentSource; + } + catch (IOException e) + { + throw new RuntimeIOException(e); + } + } + + /** + *

Gets an {@link InputStream} with the contents of a resource.

+ *

The resource must not be a directory, must exist and must return non-null to {@link Resource#newInputStream()}.

+ * + * @param resource the resource from which to get an {@link InputStream}. + * @return the {@link InputStream}. + * @throws IllegalArgumentException if the resource is a directory or does not exist or {@link Resource#newInputStream()} returns null. + */ + public static InputStream asInputStream(Resource resource) throws IllegalArgumentException + { + if (resource.isDirectory() || !resource.exists()) + throw new IllegalArgumentException("Resource must exist and cannot be a directory: " + resource); + try + { + InputStream inputStream = resource.newInputStream(); + if (inputStream == null) + throw new IllegalArgumentException("Resource does not support InputStream: " + resource); + return inputStream; + } + catch (IOException e) + { + throw new RuntimeIOException(e); + } + } + + /** + *

Performs an asynchronous copy of the contents of a resource to a sink, using the given buffer pool and buffer characteristics.

+ *

The resource must not be a directory, must exist and there must be a way to access its contents.

+ *

Multiple optimized methods are used to access the resource's contents but if they all fail, + * {@link #asContentSource(Resource, ByteBufferPool, int, boolean)} is used as a fallback to perform the + * {@link Content#copy(Content.Source, Content.Sink, Callback) copy}.

+ * + * @param resource the resource to copy from. + * @param sink the sink to copy to. + * @param bufferPool the {@link ByteBufferPool} to get buffers from. null means allocate new buffers as needed. + * @param bufferSize the size of the buffer to be used for the copy. Any value < 1 means use a default value. + * @param direct the directness of the buffers, this parameter is ignored if {@code bufferSize} is < 1. + * @param callback the callback to notify when the copy is done. + * @throws IllegalArgumentException if the resource is a directory or does not exist or there is no way to access its contents. + */ + public static void copy(Resource resource, Content.Sink sink, ByteBufferPool bufferPool, int bufferSize, boolean direct, Callback callback) throws IllegalArgumentException + { + if (resource.isDirectory() || !resource.exists()) + throw new IllegalArgumentException("Resource must exist and cannot be a directory: " + resource); + + // Save a Content.Source allocation for resources with a Path. + Path path = resource.getPath(); + if (path != null) + { + try + { + new PathToSinkCopier(path, sink, bufferPool, bufferSize, direct, callback).iterate(); + } + catch (Throwable x) + { + callback.failed(x); + } + return; + } + + // Directly write the byte array if the resource is a MemoryResource. + if (resource instanceof MemoryResource memoryResource) + { + byte[] bytes = memoryResource.getBytes(); + sink.write(true, ByteBuffer.wrap(bytes), callback); + return; + } + + // Fallback to Content.Source. + Content.Source source = asContentSource(resource, bufferPool, bufferSize, direct); + Content.copy(source, sink, callback); + } + + /** + *

Performs an asynchronous copy of a subset of the contents of a resource to a sink, using the given buffer pool and buffer characteristics.

+ *

The resource must not be a directory, must exist and there must be a way to access its contents.

+ *

Multiple optimized methods are used to access the resource's contents but if they all fail, + * {@link #asContentSource(Resource, ByteBufferPool, int, boolean, long, long)} is used as a fallback to perform the + * {@link Content#copy(Content.Source, Content.Sink, Callback) copy}.

+ * + * @param resource the resource to copy from. + * @param sink the sink to copy to. + * @param bufferPool the {@link ByteBufferPool} to get buffers from. null means allocate new buffers as needed. + * @param bufferSize the size of the buffer to be used for the copy. Any value < 1 means use a default value. + * @param direct the directness of the buffers, this parameter is ignored if {@code bufferSize} is < 1. + * @param first the first byte of the resource to start from. + * @param length the length of the resource's contents to copy. + * @param callback the callback to notify when the copy is done. + * @throws IllegalArgumentException if the resource is a directory or does not exist or there is no way to access its contents. + */ + public static void copy(Resource resource, Content.Sink sink, ByteBufferPool bufferPool, int bufferSize, boolean direct, long first, long length, Callback callback) throws IllegalArgumentException + { + if (resource.isDirectory() || !resource.exists()) + throw new IllegalArgumentException("Resource must exist and cannot be a directory: " + resource); + + // Save a Content.Source allocation for resources with a Path. + Path path = resource.getPath(); + if (path != null) + { + try + { + new PathToSinkCopier(path, sink, bufferPool, bufferSize, direct, first, length, callback).iterate(); + } + catch (Throwable x) + { + callback.failed(x); + } + return; + } + + // Directly write the byte array if the resource is a MemoryResource. + if (resource instanceof MemoryResource memoryResource) + { + byte[] bytes = memoryResource.getBytes(); + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + if (first >= 0) + byteBuffer.position((int)first); + if (length >= 0) + byteBuffer.limit((int)(byteBuffer.position() + length)); + sink.write(true, byteBuffer, callback); + return; + } + + // Fallback to Content.Source. + Content.Source source = asContentSource(resource, bufferPool, bufferSize, direct, first, length); + Content.copy(source, sink, callback); + } + + private static class PathToSinkCopier extends IteratingNestedCallback + { + private final SeekableByteChannel channel; + private final Content.Sink sink; + private final ByteBufferPool pool; + private final int bufferSize; + private final boolean direct; + private long remainingLength; + private RetainableByteBuffer retainableByteBuffer; + private boolean terminated; + + public PathToSinkCopier(Path path, Content.Sink sink, ByteBufferPool pool, int bufferSize, boolean direct, Callback callback) throws IOException + { + this(path, sink, pool, bufferSize, direct, -1L, -1L, callback); + } + + public PathToSinkCopier(Path path, Content.Sink sink, ByteBufferPool pool, int bufferSize, boolean direct, long first, long length, Callback callback) throws IOException + { + super(callback); + this.channel = Files.newByteChannel(path); + if (first > -1) + channel.position(first); + this.sink = sink; + this.pool = pool == null ? new ByteBufferPool.NonPooling() : pool; + this.bufferSize = bufferSize <= 0 ? 4096 : bufferSize; + this.direct = direct; + this.remainingLength = length; + } + + @Override + public InvocationType getInvocationType() + { + return InvocationType.NON_BLOCKING; + } + + @Override + protected Action process() throws Throwable + { + if (terminated) + return Action.SUCCEEDED; + + if (retainableByteBuffer == null) + retainableByteBuffer = pool.acquire(bufferSize, direct); + + ByteBuffer byteBuffer = retainableByteBuffer.getByteBuffer(); + BufferUtil.clearToFill(byteBuffer); + if (remainingLength >= 0 && remainingLength < Integer.MAX_VALUE) + byteBuffer.limit((int)Math.min(byteBuffer.capacity(), remainingLength)); + boolean eof = false; + while (byteBuffer.hasRemaining() && !eof) + { + int read = channel.read(byteBuffer); + if (read == -1) + eof = true; + else if (remainingLength >= 0) + remainingLength -= read; + } + BufferUtil.flipToFlush(byteBuffer, 0); + terminated = eof || remainingLength == 0; + sink.write(terminated, byteBuffer, this); + return Action.SCHEDULED; + } + + @Override + protected void onCompleteSuccess() + { + if (retainableByteBuffer != null) + retainableByteBuffer.release(); + IO.close(channel); + super.onCompleteSuccess(); + } + + @Override + protected void onCompleteFailure(Throwable x) + { + if (retainableByteBuffer != null) + retainableByteBuffer.release(); + IO.close(channel); + super.onCompleteFailure(x); + } + } + + /** + *

A specialized {@link PathContentSource} + * whose content is sliced by a byte range.

+ */ + private static class RangedPathContentSource extends PathContentSource + { + private final long first; + private final long length; + private long toRead; + + public RangedPathContentSource(Path path, ByteBufferPool bufferPool, long first, long length) + { + super(path, bufferPool); + // TODO perform sanity checks on first and length? + this.first = first; + this.length = length; + } + + @Override + protected SeekableByteChannel open() throws IOException + { + SeekableByteChannel channel = super.open(); + if (first > -1) + channel.position(first); + toRead = length; + return channel; + } + + @Override + protected int read(SeekableByteChannel channel, ByteBuffer byteBuffer) throws IOException + { + int read = super.read(channel, byteBuffer); + if (read <= 0) + return read; + + read = (int)Math.min(read, toRead); + if (read > -1) + { + toRead -= read; + byteBuffer.position(read); + } + return read; + } + + @Override + protected boolean isReadComplete(long read) + { + return read == length; + } + } + + /** + *

A specialized {@link InputStreamContentSource} + * whose content is sliced by a byte range.

+ */ + private static class RangedInputStreamContentSource extends InputStreamContentSource + { + private long toRead; + + public RangedInputStreamContentSource(InputStream inputStream, ByteBufferPool bufferPool, long first, long length) throws IOException + { + super(inputStream, bufferPool); + inputStream.skipNBytes(first); + // TODO perform sanity checks on length? + this.toRead = length; + } + + @Override + protected int fillBufferFromInputStream(InputStream inputStream, byte[] buffer) throws IOException + { + if (toRead == 0) + return -1; + int toReadInt = (int)Math.min(Integer.MAX_VALUE, toRead); + int len = toReadInt > -1 ? Math.min(toReadInt, buffer.length) : buffer.length; + int read = inputStream.read(buffer, 0, len); + toRead -= read; + return read; + } + } +} 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 c09459690cb..674d3ecf72e 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 @@ -41,6 +41,7 @@ public class InputStreamContentSource implements Content.Source private final InputStream inputStream; private final ByteBufferPool bufferPool; private int bufferSize = 4096; + private boolean useDirectByteBuffers; private Runnable demandCallback; private Content.Chunk errorChunk; private boolean closed; @@ -66,6 +67,16 @@ public class InputStreamContentSource implements Content.Source this.bufferSize = bufferSize; } + public boolean isUseDirectByteBuffers() + { + return useDirectByteBuffers; + } + + public void setUseDirectByteBuffers(boolean useDirectByteBuffers) + { + this.useDirectByteBuffers = useDirectByteBuffers; + } + @Override public Content.Chunk read() { @@ -77,7 +88,7 @@ public class InputStreamContentSource implements Content.Source return Content.Chunk.EOF; } - RetainableByteBuffer streamBuffer = bufferPool.acquire(getBufferSize(), false); + RetainableByteBuffer streamBuffer = bufferPool.acquire(getBufferSize(), useDirectByteBuffers); try { ByteBuffer buffer = streamBuffer.getByteBuffer(); diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/PathContentSource.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/PathContentSource.java index 55eb68c85c9..2b0f3bf41f4 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/PathContentSource.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/PathContentSource.java @@ -147,7 +147,7 @@ public class PathContentSource implements Content.Source if (read > 0) totalRead += read; - boolean last = isReadComplete(totalRead); + boolean last = read == -1 || isReadComplete(totalRead); if (last) IO.close(channel); 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 new file mode 100644 index 00000000000..8bbc1337760 --- /dev/null +++ b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/IOResourcesTest.java @@ -0,0 +1,182 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.io; + +import java.net.URI; +import java.util.List; +import java.util.stream.Stream; + +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceFactory; +import org.eclipse.jetty.util.resource.URLResourceFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class IOResourcesTest +{ + private ArrayByteBufferPool.Tracking bufferPool; + + @BeforeEach + public void setUp() + { + bufferPool = new ArrayByteBufferPool.Tracking(); + } + + @AfterEach + public void tearDown() + { + assertThat("Leaks: " + bufferPool.dumpLeaks(), bufferPool.getLeaks().size(), is(0)); + } + + public static Stream all() + { + URI resourceUri = MavenTestingUtils.getTestResourcePath("keystore.p12").toUri(); + return Stream.of( + ResourceFactory.root().newResource(resourceUri), + new URLResourceFactory().newResource(resourceUri) + ); + } + + @ParameterizedTest + @MethodSource("all") + public void testToRetainableByteBuffer(Resource resource) + { + RetainableByteBuffer retainableByteBuffer = IOResources.toRetainableByteBuffer(resource, bufferPool, false); + assertThat(retainableByteBuffer.remaining(), is((int)resource.length())); + retainableByteBuffer.release(); + } + + @ParameterizedTest + @MethodSource("all") + public void testAsContentSource(Resource resource) throws Exception + { + TestSink sink = new TestSink(); + Callback.Completable callback = new Callback.Completable(); + Content.Source contentSource = IOResources.asContentSource(resource, bufferPool, 1, false); + Content.copy(contentSource, sink, callback); + callback.get(); + List chunks = sink.takeAccumulatedChunks(); + long sum = chunks.stream().mapToLong(Content.Chunk::remaining).sum(); + assertThat(sum, is(resource.length())); + assertThat(chunks.get(chunks.size() - 1).isLast(), is(true)); + } + + @ParameterizedTest + @MethodSource("all") + public void testAsContentSourceWithFirst(Resource resource) throws Exception + { + TestSink sink = new TestSink(); + Callback.Completable callback = new Callback.Completable(); + Content.Source contentSource = IOResources.asContentSource(resource, bufferPool, 1, false, 100, -1); + Content.copy(contentSource, sink, callback); + callback.get(); + List chunks = sink.takeAccumulatedChunks(); + long sum = chunks.stream().mapToLong(Content.Chunk::remaining).sum(); + assertThat(sum, is(resource.length() - 100L)); + assertThat(chunks.get(chunks.size() - 1).isLast(), is(true)); + } + + @ParameterizedTest + @MethodSource("all") + public void testAsContentSourceWithLength(Resource resource) throws Exception + { + TestSink sink = new TestSink(); + Callback.Completable callback = new Callback.Completable(); + Content.Source contentSource = IOResources.asContentSource(resource, bufferPool, 1, false, -1, 500); + Content.copy(contentSource, sink, callback); + callback.get(); + List chunks = sink.takeAccumulatedChunks(); + long sum = chunks.stream().mapToLong(Content.Chunk::remaining).sum(); + assertThat(sum, is(500L)); + assertThat(chunks.get(chunks.size() - 1).isLast(), is(true)); + } + + @ParameterizedTest + @MethodSource("all") + public void testAsContentSourceWithFirstAndLength(Resource resource) throws Exception + { + TestSink sink = new TestSink(); + Callback.Completable callback = new Callback.Completable(); + Content.Source contentSource = IOResources.asContentSource(resource, bufferPool, 1, false, 100, 500); + Content.copy(contentSource, sink, callback); + callback.get(); + List chunks = sink.takeAccumulatedChunks(); + long sum = chunks.stream().mapToLong(Content.Chunk::remaining).sum(); + assertThat(sum, is(500L)); + assertThat(chunks.get(chunks.size() - 1).isLast(), is(true)); + } + + @ParameterizedTest + @MethodSource("all") + public void testCopy(Resource resource) throws Exception + { + TestSink sink = new TestSink(); + Callback.Completable callback = new Callback.Completable(); + IOResources.copy(resource, sink, bufferPool, 1, false, callback); + callback.get(); + List chunks = sink.takeAccumulatedChunks(); + long sum = chunks.stream().mapToLong(Content.Chunk::remaining).sum(); + assertThat(sum, is(resource.length())); + assertThat(chunks.get(chunks.size() - 1).isLast(), is(true)); + } + + @ParameterizedTest + @MethodSource("all") + public void testCopyWithFirst(Resource resource) throws Exception + { + TestSink sink = new TestSink(); + Callback.Completable callback = new Callback.Completable(); + IOResources.copy(resource, sink, bufferPool, 1, false, 100, -1, callback); + callback.get(); + List chunks = sink.takeAccumulatedChunks(); + long sum = chunks.stream().mapToLong(Content.Chunk::remaining).sum(); + assertThat(sum, is(resource.length() - 100L)); + assertThat(chunks.get(chunks.size() - 1).isLast(), is(true)); + } + + @ParameterizedTest + @MethodSource("all") + public void testCopyWithLength(Resource resource) throws Exception + { + TestSink sink = new TestSink(); + Callback.Completable callback = new Callback.Completable(); + IOResources.copy(resource, sink, bufferPool, 1, false, -1, 500, callback); + callback.get(); + List chunks = sink.takeAccumulatedChunks(); + long sum = chunks.stream().mapToLong(Content.Chunk::remaining).sum(); + assertThat(sum, is(500L)); + assertThat(chunks.get(chunks.size() - 1).isLast(), is(true)); + } + + @ParameterizedTest + @MethodSource("all") + public void testCopyWithFirstAndLength(Resource resource) throws Exception + { + TestSink sink = new TestSink(); + Callback.Completable callback = new Callback.Completable(); + IOResources.copy(resource, sink, bufferPool, 1, false, 100, 500, callback); + callback.get(); + List chunks = sink.takeAccumulatedChunks(); + long sum = chunks.stream().mapToLong(Content.Chunk::remaining).sum(); + assertThat(sum, is(500L)); + assertThat(chunks.get(chunks.size() - 1).isLast(), is(true)); + } +} diff --git a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/TestSink.java b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/TestSink.java index e270e2bd0fc..2ae06b1a0ed 100644 --- a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/TestSink.java +++ b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/TestSink.java @@ -17,6 +17,7 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; public class TestSink implements Content.Sink @@ -26,7 +27,7 @@ public class TestSink implements Content.Sink @Override public void write(boolean last, ByteBuffer byteBuffer, Callback callback) { - accumulatedChunks.add(Content.Chunk.from(byteBuffer, last)); + accumulatedChunks.add(Content.Chunk.from(BufferUtil.copy(byteBuffer), last)); callback.succeeded(); } diff --git a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java index 521cce08849..aa5887299bc 100644 --- a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java +++ b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Properties; import java.util.stream.Collectors; +import org.eclipse.jetty.io.IOResources; import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.resource.ResourceFactory; import org.eclipse.jetty.util.security.Credential; @@ -96,7 +97,7 @@ public class JDBCLoginService extends AbstractLoginService { Properties properties = new Properties(); try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable(); - InputStream in = resourceFactory.newResource(_config).newInputStream()) + InputStream in = IOResources.asInputStream(resourceFactory.newResource(_config))) { properties.load(in); } diff --git a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java index fbebcab0c94..4d59d2cfa70 100644 --- a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java +++ b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import org.eclipse.jetty.io.IOResources; import org.eclipse.jetty.util.Scanner; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.resource.Resource; @@ -158,10 +159,8 @@ public class PropertyUserStore extends UserStore implements Scanner.DiscreteList throw new IllegalStateException("Config does not exist: " + config); Properties properties = new Properties(); - try (InputStream inputStream = config.newInputStream()) + try (InputStream inputStream = IOResources.asInputStream(config)) { - if (inputStream == null) - throw new IllegalStateException("Config does have properties: " + config); properties.load(inputStream); } 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 8660097a478..566b1b676bc 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 @@ -15,10 +15,8 @@ package org.eclipse.jetty.server; import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.channels.ReadableByteChannel; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -44,14 +42,10 @@ import org.eclipse.jetty.http.QuotedCSV; import org.eclipse.jetty.http.QuotedQualityCSV; import org.eclipse.jetty.http.content.HttpContent; import org.eclipse.jetty.http.content.PreCompressedHttpContent; -import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.Content; -import org.eclipse.jetty.io.RetainableByteBuffer; +import org.eclipse.jetty.io.IOResources; import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.IteratingCallback; import org.eclipse.jetty.util.URIUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -686,15 +680,8 @@ public class ResourceService response.setStatus(HttpStatus.PARTIAL_CONTENT_206); response.getHeaders().put(HttpHeader.CONTENT_RANGE, range.toHeaderValue(contentLength)); - // Try using the resource's path if possible, as the nio API is async and helps to avoid buffer copies. - Path path = content.getResource().getPath(); - Content.Source source; - if (path != null) - source = new MultiPartByteRanges.PathContentSource(path, range); - else - source = new MultiPartByteRanges.InputStreamContentSource(content.getResource().newInputStream(), range); - - Content.copy(source, response, callback); + // TODO use a buffer pool + IOResources.copy(content.getResource(), response, null, 0, false, range.first(), range.getLength(), callback); return; } @@ -703,7 +690,7 @@ public class ResourceService String contentType = "multipart/byteranges; boundary="; String boundary = MultiPart.generateBoundary(null, 24); MultiPartByteRanges.ContentSource byteRanges = new MultiPartByteRanges.ContentSource(boundary); - ranges.forEach(range -> byteRanges.addPart(new MultiPartByteRanges.Part(content.getContentTypeValue(), content.getResource(), range, contentLength))); + ranges.forEach(range -> byteRanges.addPart(new MultiPartByteRanges.Part(content.getContentTypeValue(), content.getResource(), range, contentLength, request.getComponents().getByteBufferPool()))); byteRanges.close(); long partsContentLength = byteRanges.getLength(); putHeaders(response, content, partsContentLength); @@ -717,9 +704,18 @@ public class ResourceService { ByteBuffer buffer = content.getByteBuffer(); // this buffer is going to be consumed by response.write() if (buffer != null) + { response.write(true, buffer, callback); + } else - new ContentWriterIteratingCallback(content, response, callback).iterate(); + { + IOResources.copy( + content.getResource(), + response, request.getComponents().getByteBufferPool(), + request.getConnectionMetaData().getHttpConfiguration().getOutputBufferSize(), + request.getConnectionMetaData().getHttpConfiguration().isUseOutputDirectByteBuffers(), + callback); + } } catch (Throwable x) { @@ -917,57 +913,4 @@ public class ResourceService */ String getWelcomeTarget(HttpContent content, Request request) throws IOException; } - - private static class ContentWriterIteratingCallback extends IteratingCallback - { - private final ReadableByteChannel source; - private final Content.Sink sink; - private final Callback callback; - private final RetainableByteBuffer buffer; - - public ContentWriterIteratingCallback(HttpContent content, Response target, Callback callback) throws IOException - { - this.source = content.getResource().newReadableByteChannel(); - this.sink = target; - this.callback = callback; - ByteBufferPool bufferPool = target.getRequest().getComponents().getByteBufferPool(); - int outputBufferSize = target.getRequest().getConnectionMetaData().getHttpConfiguration().getOutputBufferSize(); - boolean useOutputDirectByteBuffers = target.getRequest().getConnectionMetaData().getHttpConfiguration().isUseOutputDirectByteBuffers(); - this.buffer = bufferPool.acquire(outputBufferSize, useOutputDirectByteBuffers); - } - - @Override - protected Action process() throws Throwable - { - if (!source.isOpen()) - return Action.SUCCEEDED; - - ByteBuffer byteBuffer = buffer.getByteBuffer(); - BufferUtil.clearToFill(byteBuffer); - int read = source.read(byteBuffer); - if (read == -1) - { - IO.close(source); - sink.write(true, BufferUtil.EMPTY_BUFFER, this); - return Action.SCHEDULED; - } - BufferUtil.flipToFlush(byteBuffer, 0); - sink.write(false, byteBuffer, this); - return Action.SCHEDULED; - } - - @Override - protected void onCompleteSuccess() - { - buffer.release(); - callback.succeeded(); - } - - @Override - protected void onCompleteFailure(Throwable x) - { - buffer.release(); - callback.failed(x); - } - } } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java index b49786fb5f4..e375f16e1a6 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java @@ -28,6 +28,7 @@ import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.http.PreEncodedHttpField; +import org.eclipse.jetty.io.IOResources; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; @@ -97,7 +98,7 @@ public class DefaultHandler extends Handler.Abstract Resource faviconRes = server.getDefaultFavicon(); if (faviconRes != null) { - try (InputStream is = faviconRes.newInputStream()) + try (InputStream is = IOResources.asInputStream(faviconRes)) { favbytes = IO.readBytes(is); } diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/DebugHandlerTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/DebugHandlerTest.java index daf1d71adc2..afb4663a9b1 100644 --- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/DebugHandlerTest.java +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/DebugHandlerTest.java @@ -27,6 +27,7 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import org.eclipse.jetty.io.ArrayByteBufferPool; +import org.eclipse.jetty.io.IOResources; import org.eclipse.jetty.server.AbstractConnectionFactory; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.Server; @@ -106,7 +107,7 @@ public class DebugHandlerTest secureServerURI = URI.create(String.format("https://%s:%d/", host, sslConnector.getLocalPort())); KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); - try (InputStream stream = sslContextFactory.getKeyStoreResource().newInputStream()) + try (InputStream stream = IOResources.asInputStream(sslContextFactory.getKeyStoreResource())) { keystore.load(stream, "storepwd".toCharArray()); } 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 bada49b43cb..7dc7308d08e 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 @@ -28,7 +28,9 @@ import org.eclipse.jetty.http.MultiPart; import org.eclipse.jetty.http.MultiPartByteRanges; import org.eclipse.jetty.http.content.HttpContent; import org.eclipse.jetty.http.content.ResourceHttpContent; +import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.io.IOResources; import org.eclipse.jetty.io.content.ByteBufferContentSource; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.ResourceService; @@ -172,7 +174,7 @@ public class ResourceHandlerByteRangesTest { return path -> new ResourceHttpContent(memResource, "text/plain") { - final ByteBuffer buffer = BufferUtil.toBuffer(getResource(), false); + final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), new ByteBufferPool.NonPooling(), false).getByteBuffer(); @Override public ByteBuffer getByteBuffer() @@ -213,7 +215,7 @@ public class ResourceHandlerByteRangesTest { return path -> new ResourceHttpContent(memResource, "text/plain") { - final ByteBuffer buffer = BufferUtil.toBuffer(getResource(), false); + final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), new ByteBufferPool.NonPooling(), false).getByteBuffer(); @Override public ByteBuffer getByteBuffer() diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/ServerConnectorSslServerTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/ServerConnectorSslServerTest.java index 5e3d85d2299..1114f93d786 100644 --- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/ServerConnectorSslServerTest.java +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/ServerConnectorSslServerTest.java @@ -30,6 +30,7 @@ import javax.net.ssl.TrustManagerFactory; import org.eclipse.jetty.io.ArrayByteBufferPool; import org.eclipse.jetty.io.Content; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.IOResources; import org.eclipse.jetty.server.AbstractConnectionFactory; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; @@ -88,7 +89,7 @@ public class ServerConnectorSslServerTest extends HttpServerTestBase initServer(connector); KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); - try (InputStream stream = sslContextFactory.getKeyStoreResource().newInputStream()) + try (InputStream stream = IOResources.asInputStream(sslContextFactory.getKeyStoreResource())) { keystore.load(stream, "storepwd".toCharArray()); } 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 27b5d9758b4..3d771910596 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 @@ -21,6 +21,7 @@ import java.nio.Buffer; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; import java.nio.channels.ReadableByteChannel; @@ -1102,7 +1103,7 @@ public class BufferUtil ByteBuffer buffer = direct ? BufferUtil.allocateDirect(len) : BufferUtil.allocate(len); int pos = BufferUtil.flipToFill(buffer); - try (ReadableByteChannel channel = resource.newReadableByteChannel()) + try (ReadableByteChannel channel = Channels.newChannel(resource.newInputStream())) { long needed = len; while (needed > 0 && buffer.hasRemaining()) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/MemoryResource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/MemoryResource.java index b2b6df3d6fc..31970e17cf1 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/MemoryResource.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/MemoryResource.java @@ -55,6 +55,11 @@ public class MemoryResource extends Resource } } + public byte[] getBytes() + { + return _bytes; + } + @Override public Path getPath() { diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java index b095f922e49..5eb8dd2f0cc 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java @@ -347,8 +347,7 @@ public class PathResource extends Resource } catch (IOException e) { - // in case of error, use Files.size() logic of 0L - return 0L; + return -1L; } } diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java index 1abb4d4a323..d88930e1d6c 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java @@ -238,7 +238,9 @@ public abstract class Resource implements Iterable * * @return a readable {@link java.nio.channels.ByteChannel} to the resource or null if one is not available. * @throws IOException if unable to open the readable bytechannel for the resource. + * @deprecated use {@link #newInputStream()} or {@code IOResources} instead. */ + @Deprecated(since = "12.0.8", forRemoval = true) public ReadableByteChannel newReadableByteChannel() throws IOException { Path path = getPath(); diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileSystemResourceTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileSystemResourceTest.java index ab89e145738..20c1c0e7aa7 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileSystemResourceTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileSystemResourceTest.java @@ -22,8 +22,6 @@ import java.io.StringReader; import java.io.StringWriter; import java.net.URI; import java.net.URL; -import java.nio.ByteBuffer; -import java.nio.channels.ReadableByteChannel; import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryStream; import java.nio.file.FileSystem; @@ -39,7 +37,6 @@ import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.toolchain.test.IO; import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; -import org.eclipse.jetty.util.BufferUtil; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; @@ -474,31 +471,6 @@ public class FileSystemResourceTest } } - @Test - public void testReadableByteChannel(WorkDir workDir) throws Exception - { - Path dir = workDir.getEmptyPathDir(); - Path file = dir.resolve("foo"); - String content = "Foo is here"; - - try (StringReader reader = new StringReader(content); - BufferedWriter writer = Files.newBufferedWriter(file)) - { - IO.copy(reader, writer); - } - - Resource base = ResourceFactory.root().newResource(dir); - Resource foo = base.resolve("foo"); - try (ReadableByteChannel channel = foo.newReadableByteChannel()) - { - ByteBuffer buf = ByteBuffer.allocate(256); - channel.read(buf); - buf.flip(); - String actual = BufferUtil.toUTF8String(buf); - assertThat("ReadableByteChannel content", actual, is(content)); - } - } - @Test public void testGetURI(WorkDir workDir) throws Exception { diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/PathResourceTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/PathResourceTest.java index c0739721063..a74b77acb85 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/PathResourceTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/PathResourceTest.java @@ -16,7 +16,6 @@ package org.eclipse.jetty.util.resource; import java.io.IOException; import java.io.InputStream; import java.net.URI; -import java.nio.channels.ReadableByteChannel; import java.nio.charset.StandardCharsets; import java.nio.file.ClosedFileSystemException; import java.nio.file.FileSystem; @@ -96,26 +95,6 @@ public class PathResourceTest } } - @Test - public void testNonDefaultFileSystemGetReadableByteChannel() throws IOException - { - Path exampleJar = MavenTestingUtils.getTestResourcePathFile("example.jar"); - - try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable()) - { - Resource jarFileResource = resourceFactory.newJarFileResource(exampleJar.toUri()); - Path manifestPath = jarFileResource.getPath().resolve("/META-INF/MANIFEST.MF"); - assertThat(manifestPath, is(not(nullValue()))); - - PathResource resource = (PathResource)resourceFactory.newResource(manifestPath); - - try (ReadableByteChannel channel = resource.newReadableByteChannel()) - { - assertThat("ReadableByteChannel", channel, is(not(nullValue()))); - } - } - } - @Test public void testNonDefaultFileSystemGetPath() { diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java index a3d939a2a51..035d8440247 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java @@ -336,7 +336,7 @@ public class ResourceTest } @Test - public void testNonExistentResource() + public void testNonExistentResource() throws IOException { Path nonExistentFile = workDir.getPathFile("does-not-exists"); Resource resource = resourceFactory.newResource(nonExistentFile); @@ -348,7 +348,7 @@ public class ResourceTest assertFalse(resource.isReadable()); assertEquals(nonExistentFile, resource.getPath()); assertEquals(Instant.EPOCH, resource.lastModified()); - assertEquals(0L, resource.length()); + assertEquals(-1L, resource.length()); assertThrows(IOException.class, resource::newInputStream); assertThrows(IOException.class, resource::newReadableByteChannel); assertEquals(nonExistentFile.toUri(), resource.getURI()); diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/UrlResourceFactoryTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/UrlResourceFactoryTest.java index 7f028f4b5a9..01ef867bbd4 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/UrlResourceFactoryTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/UrlResourceFactoryTest.java @@ -19,7 +19,6 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.nio.ByteBuffer; -import java.nio.channels.ReadableByteChannel; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -31,6 +30,7 @@ import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.URIUtil; import org.junit.jupiter.api.Tag; @@ -186,12 +186,8 @@ public class UrlResourceFactoryTest assertThat(resource.isDirectory(), is(false)); - try (ReadableByteChannel channel = resource.newReadableByteChannel()) - { - ByteBuffer buffer = ByteBuffer.allocate(fileSize); - int read = channel.read(buffer); - assertThat(read, is(fileSize)); - } + ByteBuffer buffer = BufferUtil.toBuffer(resource, false); + assertThat(buffer.remaining(), is(fileSize)); } @Test @@ -213,12 +209,8 @@ public class UrlResourceFactoryTest assertThat(resource.isDirectory(), is(false)); - try (ReadableByteChannel channel = resource.newReadableByteChannel()) - { - ByteBuffer buffer = ByteBuffer.allocate(fileSize); - int read = channel.read(buffer); - assertThat(read, is(fileSize)); - } + ByteBuffer buffer = BufferUtil.toBuffer(resource, false); + assertThat(buffer.remaining(), is(fileSize)); } @Test diff --git a/jetty-ee10/jetty-ee10-maven-plugin/src/main/java/org/eclipse/jetty/ee10/maven/plugin/MavenResource.java b/jetty-ee10/jetty-ee10-maven-plugin/src/main/java/org/eclipse/jetty/ee10/maven/plugin/MavenResource.java index 277df9c0a60..ab2f6ca7a35 100644 --- a/jetty-ee10/jetty-ee10-maven-plugin/src/main/java/org/eclipse/jetty/ee10/maven/plugin/MavenResource.java +++ b/jetty-ee10/jetty-ee10-maven-plugin/src/main/java/org/eclipse/jetty/ee10/maven/plugin/MavenResource.java @@ -16,7 +16,6 @@ package org.eclipse.jetty.ee10.maven.plugin; import java.io.IOException; import java.io.InputStream; import java.net.URI; -import java.nio.channels.ReadableByteChannel; import java.nio.file.Path; import java.time.Instant; import java.util.Collection; @@ -211,14 +210,6 @@ public class MavenResource extends Resource return _resource.newInputStream(); } - @Override - public ReadableByteChannel newReadableByteChannel() throws IOException - { - if (_resource == null) - return null; - return _resource.newReadableByteChannel(); - } - @Override public Resource resolve(String subUriPath) { diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java index 75cabf6446e..f6c288d3108 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java @@ -75,6 +75,7 @@ import org.eclipse.jetty.ee10.servlet.security.ConstraintMapping; import org.eclipse.jetty.ee10.servlet.security.ConstraintSecurityHandler; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.pathmap.MatchedResource; +import org.eclipse.jetty.io.IOResources; import org.eclipse.jetty.security.SecurityHandler; import org.eclipse.jetty.server.Context; import org.eclipse.jetty.server.FormFields; @@ -2843,7 +2844,7 @@ public class ServletContextHandler extends ContextHandler // Cannot serve directories as an InputStream if (r.isDirectory()) return null; - return r.newInputStream(); + return IOResources.asInputStream(r); } catch (Exception e) { 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 b950f19e944..cc9821ae9f0 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 @@ -53,6 +53,8 @@ import org.eclipse.jetty.http.HttpTester; import org.eclipse.jetty.http.UriCompliance; import org.eclipse.jetty.http.content.ResourceHttpContent; import org.eclipse.jetty.http.content.ResourceHttpContentFactory; +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.IOResources; import org.eclipse.jetty.logging.StacklessLogging; import org.eclipse.jetty.server.AllowedResourceAliasChecker; import org.eclipse.jetty.server.HttpConfiguration; @@ -3468,7 +3470,7 @@ public class DefaultServletTest context.addServlet(new ServletHolder(defaultServlet), "/"); defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain") { - final ByteBuffer buffer = BufferUtil.toBuffer(getResource(), false); + final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), new ByteBufferPool.NonPooling(), false).getByteBuffer(); @Override public ByteBuffer getByteBuffer() @@ -3522,7 +3524,7 @@ public class DefaultServletTest context.addServlet(new ServletHolder(defaultServlet), "/"); defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain") { - final ByteBuffer buffer = BufferUtil.toBuffer(getResource(), false); + final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), new ByteBufferPool.NonPooling(), false).getByteBuffer(); @Override public ByteBuffer getByteBuffer() diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-sessions/jetty-ee10-test-sessions-common/src/main/java/org/eclipse/jetty/ee10/session/AbstractWebAppObjectInSessionTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-sessions/jetty-ee10-test-sessions-common/src/main/java/org/eclipse/jetty/ee10/session/AbstractWebAppObjectInSessionTest.java index 4fbe773a685..beb1a9ebdb1 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-sessions/jetty-ee10-test-sessions-common/src/main/java/org/eclipse/jetty/ee10/session/AbstractWebAppObjectInSessionTest.java +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-sessions/jetty-ee10-test-sessions-common/src/main/java/org/eclipse/jetty/ee10/session/AbstractWebAppObjectInSessionTest.java @@ -14,7 +14,6 @@ package org.eclipse.jetty.ee10.session; import java.io.File; -import java.io.FileOutputStream; import java.io.FileWriter; import jakarta.servlet.http.HttpServletResponse; @@ -29,7 +28,6 @@ import org.eclipse.jetty.session.DefaultSessionCacheFactory; import org.eclipse.jetty.session.SessionCache; import org.eclipse.jetty.session.SessionDataStoreFactory; import org.eclipse.jetty.session.test.AbstractSessionTestBase; -import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.ResourceFactory; import org.junit.jupiter.api.Test; @@ -87,14 +85,14 @@ public abstract class AbstractWebAppObjectInSessionTest extends AbstractSessionT //File sourceFile = new File(getClass().getClassLoader().getResource(resourceName).toURI()); File targetFile = new File(packageDirs, resourceName); //copy(sourceFile, targetFile); - IO.copy(resource.newInputStream(), new FileOutputStream(targetFile)); + resource.copyTo(targetFile.toPath()); resourceName = WebAppObjectInSessionServlet.class.getSimpleName() + "$" + WebAppObjectInSessionServlet.TestSharedStatic.class.getSimpleName() + ".class"; resource = resourceFactory.newResource(getClass().getResource(packageName + resourceName)); //sourceFile = new File(getClass().getClassLoader().getResource(resourceName).toURI()); targetFile = new File(packageDirs, resourceName); //copy(sourceFile, targetFile); - IO.copy(resource.newInputStream(), new FileOutputStream(targetFile)); + resource.copyTo(targetFile.toPath()); } DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); diff --git a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/Descriptor.java b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/Descriptor.java index 9e072e325ca..68827b2abcb 100644 --- a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/Descriptor.java +++ b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/Descriptor.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.Objects; +import org.eclipse.jetty.io.IOResources; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.xml.XmlParser; import org.slf4j.Logger; @@ -44,7 +45,7 @@ public abstract class Descriptor if (_root == null) { Objects.requireNonNull(parser); - try (InputStream is = _xml.newInputStream()) + try (InputStream is = IOResources.asInputStream(_xml)) { _root = parser.parse(is); _dtd = parser.getDTD(); diff --git a/jetty-ee9/jetty-ee9-maven-plugin/src/main/java/org/eclipse/jetty/ee9/maven/plugin/MavenResource.java b/jetty-ee9/jetty-ee9-maven-plugin/src/main/java/org/eclipse/jetty/ee9/maven/plugin/MavenResource.java index 02ede13bdd3..78d6783cc5a 100644 --- a/jetty-ee9/jetty-ee9-maven-plugin/src/main/java/org/eclipse/jetty/ee9/maven/plugin/MavenResource.java +++ b/jetty-ee9/jetty-ee9-maven-plugin/src/main/java/org/eclipse/jetty/ee9/maven/plugin/MavenResource.java @@ -16,7 +16,6 @@ package org.eclipse.jetty.ee9.maven.plugin; import java.io.IOException; import java.io.InputStream; import java.net.URI; -import java.nio.channels.ReadableByteChannel; import java.nio.file.Path; import java.time.Instant; import java.util.Collection; @@ -211,14 +210,6 @@ public class MavenResource extends Resource return _resource.newInputStream(); } - @Override - public ReadableByteChannel newReadableByteChannel() throws IOException - { - if (_resource == null) - return null; - return _resource.newReadableByteChannel(); - } - @Override public Resource resolve(String subUriPath) { diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java index 6a92c382522..073740950cf 100644 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java +++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java @@ -66,6 +66,7 @@ import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.io.IOResources; import org.eclipse.jetty.server.AliasCheck; import org.eclipse.jetty.server.AllowedResourceAliasChecker; import org.eclipse.jetty.server.Context; @@ -1980,7 +1981,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Supplie // Cannot serve directories as an InputStream if (r.isDirectory()) return null; - return r.newInputStream(); + return IOResources.asInputStream(r); } catch (Exception e) { 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 602b417e88f..c69bb366410 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 @@ -33,6 +33,7 @@ import jakarta.servlet.ServletResponse; import jakarta.servlet.WriteListener; import org.eclipse.jetty.http.content.HttpContent; import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.IOResources; import org.eclipse.jetty.io.RetainableByteBuffer; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.util.BufferUtil; @@ -43,6 +44,7 @@ import org.eclipse.jetty.util.IteratingCallback; import org.eclipse.jetty.util.NanoTime; import org.eclipse.jetty.util.SharedBlockingCallback; import org.eclipse.jetty.util.SharedBlockingCallback.Blocker; +import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.ThreadIdPool; import org.slf4j.Logger; @@ -1166,7 +1168,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable { try (Blocker blocker = _writeBlocker.acquire()) { - new InputStreamWritingCB(in, blocker).iterate(); + sendContent(in, blocker); blocker.block(); } } @@ -1181,7 +1183,22 @@ public class HttpOutput extends ServletOutputStream implements Runnable { try (Blocker blocker = _writeBlocker.acquire()) { - new ReadableByteChannelWritingCB(in, blocker).iterate(); + sendContent(in, blocker); + blocker.block(); + } + } + + /** + * Blocking send of resource. + * + * @param resource The resource content to send + * @throws IOException if the send fails + */ + public void sendContent(Resource resource) throws IOException + { + try (Blocker blocker = _writeBlocker.acquire()) + { + sendContent(resource, blocker); blocker.block(); } } @@ -1213,23 +1230,24 @@ public class HttpOutput extends ServletOutputStream implements Runnable LOG.debug("sendContent(buffer={},{})", BufferUtil.toDetailString(content), callback); if (prepareSendContent(content.remaining(), callback)) - channelWrite(content, true, - new Callback.Nested(callback) + { + channelWrite(content, true, new Callback.Nested(callback) + { + @Override + public void succeeded() { - @Override - public void succeeded() - { - onWriteComplete(true, null); - super.succeeded(); - } + onWriteComplete(true, null); + super.succeeded(); + } - @Override - public void failed(Throwable x) - { - onWriteComplete(true, x); - super.failed(x); - } - }); + @Override + public void failed(Throwable x) + { + onWriteComplete(true, x); + super.failed(x); + } + }); + } } /** @@ -1264,6 +1282,67 @@ public class HttpOutput extends ServletOutputStream implements Runnable new ReadableByteChannelWritingCB(in, callback).iterate(); } + /** + * Asynchronous send of whole resource. + * + * @param resource The resource content to send + * @param callback The callback to use to notify success or failure + */ + public void sendContent(Resource resource, Callback callback) + { + try + { + if (prepareSendContent(0, callback)) + { + IOResources.copy(resource, (last, byteBuffer, cb) -> + { + _written += byteBuffer.remaining(); + channelWrite(byteBuffer, last, cb); + }, _channel.getByteBufferPool(), getBufferSize(), _channel.isUseOutputDirectByteBuffers(), new Callback.Nested(callback) + { + @Override + public void succeeded() + { + onWriteComplete(true, null); + super.succeeded(); + } + + @Override + public void failed(Throwable x) + { + onWriteComplete(true, x); + super.failed(x); + } + }); + } + } + catch (Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("Unable to send resource {}", resource, x); + _channel.abort(x); + callback.failed(x); + } + } + + /** + * Asynchronous send of HTTP content. + * + * @param httpContent The HTTP content to send + * @param callback The callback to use to notify success or failure + */ + public void sendContent(HttpContent httpContent, Callback callback) + { + if (LOG.isDebugEnabled()) + LOG.debug("sendContent(http={},{})", httpContent, callback); + + ByteBuffer buffer = httpContent.getByteBuffer(); + if (buffer != null) + sendContent(buffer, callback); + else + sendContent(httpContent.getResource(), callback); + } + private boolean prepareSendContent(int len, Callback callback) { try (AutoLock l = _channelState.lock()) @@ -1306,38 +1385,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable } } - /** - * Asynchronous send of HTTP content. - * - * @param httpContent The HTTP content to send - * @param callback The callback to use to notify success or failure - */ - public void sendContent(HttpContent httpContent, Callback callback) - { - if (LOG.isDebugEnabled()) - LOG.debug("sendContent(http={},{})", httpContent, callback); - - ByteBuffer buffer = httpContent.getByteBuffer(); - if (buffer != null) - { - sendContent(buffer, callback); - return; - } - - try - { - ReadableByteChannel rbc = httpContent.getResource().newReadableByteChannel(); - sendContent(rbc, callback); - } - catch (Throwable x) - { - if (LOG.isDebugEnabled()) - LOG.debug("Unable to access ReadableByteChannel for content {}", httpContent, x); - _channel.abort(x); - callback.failed(x); - } - } - public int getBufferSize() { return _bufferSize; 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 c23ed27fbd0..5b47cdaf0ed 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 @@ -50,6 +50,7 @@ import org.eclipse.jetty.http.QuotedCSV; import org.eclipse.jetty.http.QuotedQualityCSV; import org.eclipse.jetty.http.content.HttpContent; import org.eclipse.jetty.http.content.PreCompressedHttpContent; +import org.eclipse.jetty.io.IOResources; import org.eclipse.jetty.io.WriterOutputStream; import org.eclipse.jetty.server.ResourceListing; import org.eclipse.jetty.util.BufferUtil; @@ -897,7 +898,7 @@ public class ResourceService } // Perform ranged write - try (InputStream input = content.getResource().newInputStream()) + try (InputStream input = IOResources.asInputStream(content.getResource())) { input.skipNBytes(start); IO.copy(input, out, contentLength); 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 index 22594625abf..6acefaff35c 100644 --- 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 @@ -19,6 +19,7 @@ import java.nio.file.Path; import java.util.Objects; import org.eclipse.jetty.http.content.HttpContent; +import org.eclipse.jetty.io.IOResources; /** * Range Writer selection for HttpContent @@ -46,6 +47,6 @@ public class HttpContentRangeWriter return new SeekableByteChannelRangeWriter(() -> Files.newByteChannel(path)); // Fallback to InputStream - return new InputStreamRangeWriter(() -> content.getResource().newInputStream()); + return new InputStreamRangeWriter(() -> IOResources.asInputStream(content.getResource())); } } 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 index 51f66313c5c..431fbaec8f8 100644 --- 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 @@ -114,6 +114,7 @@ public class InputStreamRangeWriter implements RangeWriter pos = skipTo; } + // TODO this is very inefficient as copy() allocates a 64K buffer. IO.copy(inputStream, outputStream, length); pos += length; } diff --git a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/HttpOutputTest.java b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/HttpOutputTest.java index d4bab1d0cb0..e0fd78fe892 100644 --- a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/HttpOutputTest.java +++ b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/HttpOutputTest.java @@ -21,6 +21,7 @@ import java.io.PrintWriter; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -31,6 +32,7 @@ import jakarta.servlet.ServletException; import jakarta.servlet.WriteListener; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.io.IOResources; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.LocalConnector.LocalEndPoint; @@ -138,7 +140,7 @@ public class HttpOutputTest public void testSendInputStreamSimple() throws Exception { Resource simple = ResourceFactory.of(_contextHandler).newClassPathResource("simple/simple.txt"); - _handler._contentInputStream = simple.newInputStream(); + _handler._contentInputStream = IOResources.asInputStream(simple); String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); assertThat(response, containsString("HTTP/1.1 200 OK")); assertThat(response, containsString("Content-Length: 11")); @@ -148,7 +150,7 @@ public class HttpOutputTest public void testSendInputStreamBig() throws Exception { Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); - _handler._contentInputStream = big.newInputStream(); + _handler._contentInputStream = IOResources.asInputStream(big); String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); assertThat(response, containsString("HTTP/1.1 200 OK")); assertThat(response, Matchers.not(containsString("Content-Length"))); @@ -159,7 +161,7 @@ public class HttpOutputTest public void testSendInputStreamBigChunked() throws Exception { Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); - _handler._contentInputStream = new FilterInputStream(big.newInputStream()) + _handler._contentInputStream = new FilterInputStream(IOResources.asInputStream(big)) { @Override public int read(byte[] b, int off, int len) throws IOException @@ -190,7 +192,7 @@ public class HttpOutputTest public void testSendChannelSimple() throws Exception { Resource simple = ResourceFactory.of(_contextHandler).newClassPathResource("simple/simple.txt"); - _handler._contentChannel = simple.newReadableByteChannel(); + _handler._contentChannel = Files.newByteChannel(simple.getPath()); String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); assertThat(response, containsString("HTTP/1.1 200 OK")); assertThat(response, containsString("Content-Length: 11")); @@ -200,7 +202,29 @@ public class HttpOutputTest public void testSendChannelBig() throws Exception { Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); - _handler._contentChannel = big.newReadableByteChannel(); + _handler._contentChannel = Files.newByteChannel(big.getPath()); + String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); + assertThat(response, containsString("HTTP/1.1 200 OK")); + assertThat(response, Matchers.not(containsString("Content-Length"))); + assertThat(response, endsWith(toUTF8String(big))); + } + + @Test + public void testSendResourceSimple() throws Exception + { + Resource simple = ResourceFactory.of(_contextHandler).newClassPathResource("simple/simple.txt"); + _handler._contentResource = simple; + String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); + assertThat(response, containsString("HTTP/1.1 200 OK")); + assertThat(response, containsString("Content-Length: 11")); + assertThat(response, endsWith(toUTF8String(simple))); + } + + @Test + public void testSendResourceBig() throws Exception + { + Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); + _handler._contentResource = big; String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); assertThat(response, containsString("HTTP/1.1 200 OK")); assertThat(response, Matchers.not(containsString("Content-Length"))); @@ -211,7 +235,7 @@ public class HttpOutputTest public void testSendBigDirect() throws Exception { Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); - _handler._content = BufferUtil.toBuffer(big, true); + _handler._content = IOResources.toRetainableByteBuffer(big, null, true).getByteBuffer(); String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); assertThat(response, containsString("HTTP/1.1 200 OK")); assertThat(response, containsString("Content-Length")); @@ -222,7 +246,7 @@ public class HttpOutputTest public void testSendBigInDirect() throws Exception { Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); assertThat(response, containsString("HTTP/1.1 200 OK")); assertThat(response, containsString("Content-Length")); @@ -233,7 +257,7 @@ public class HttpOutputTest public void testSendChannelBigChunked() throws Exception { Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); - final ReadableByteChannel channel = big.newReadableByteChannel(); + final ReadableByteChannel channel = Files.newByteChannel(big.getPath()); _handler._contentChannel = new ReadableByteChannel() { @Override @@ -287,7 +311,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = false; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._arrayBuffer = new byte[1]; String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); @@ -301,7 +325,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = false; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._arrayBuffer = new byte[8]; String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); @@ -315,7 +339,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = false; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._arrayBuffer = new byte[4000]; String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); @@ -329,7 +353,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = false; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._arrayBuffer = new byte[8192]; String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); @@ -343,7 +367,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = true; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._arrayBuffer = new byte[1]; String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); @@ -358,7 +382,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = true; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._arrayBuffer = new byte[8]; String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); @@ -373,7 +397,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = true; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._arrayBuffer = new byte[4000]; String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); @@ -388,7 +412,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = true; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._arrayBuffer = new byte[8192]; String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); @@ -421,7 +445,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = false; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._byteBuffer = BufferUtil.allocate(8); String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); @@ -436,7 +460,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = false; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._byteBuffer = BufferUtil.allocate(4000); String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); @@ -451,7 +475,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = false; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._byteBuffer = BufferUtil.allocate(8192); String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); @@ -466,7 +490,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = true; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._byteBuffer = BufferUtil.allocate(8); String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); @@ -481,7 +505,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = true; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._byteBuffer = BufferUtil.allocate(4000); String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); @@ -496,7 +520,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = true; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._byteBuffer = BufferUtil.allocate(8192); String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); @@ -511,7 +535,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = false; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._arrayBuffer = new byte[1]; _handler._async = true; @@ -527,7 +551,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = false; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._arrayBuffer = new byte[8]; _handler._async = true; @@ -543,7 +567,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = false; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._arrayBuffer = new byte[4000]; _handler._async = true; @@ -559,7 +583,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = false; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._arrayBuffer = new byte[8192]; _handler._async = true; @@ -594,7 +618,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = false; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._byteBuffer = BufferUtil.allocate(8); _handler._async = true; @@ -610,7 +634,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = false; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._byteBuffer = BufferUtil.allocate(4000); _handler._async = true; @@ -626,7 +650,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = false; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._byteBuffer = BufferUtil.allocate(8192); _handler._async = true; @@ -643,7 +667,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = false; - _handler._content = BufferUtil.toBuffer(big, true); + _handler._content = IOResources.toRetainableByteBuffer(big, null, true).getByteBuffer(); _handler._byteBuffer = BufferUtil.allocateDirect(8192); _handler._async = true; @@ -659,7 +683,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = false; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._byteBuffer = BufferUtil.allocate(8192); _handler._async = true; @@ -679,7 +703,7 @@ public class HttpOutputTest _handler._async = true; _handler._writeLengthIfKnown = true; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._arrayBuffer = new byte[4000]; String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); @@ -696,7 +720,7 @@ public class HttpOutputTest _handler._async = true; _handler._writeLengthIfKnown = true; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._arrayBuffer = new byte[4000]; int start = _handler._owp.get(); @@ -712,7 +736,7 @@ public class HttpOutputTest { final Resource big = ResourceFactory.of(_contextHandler).newClassPathResource("simple/big.txt"); _handler._writeLengthIfKnown = false; - _handler._content = BufferUtil.toBuffer(big, false); + _handler._content = IOResources.toRetainableByteBuffer(big, null, false).getByteBuffer(); _handler._arrayBuffer = new byte[1024]; _handler._interceptor = new ChainedInterceptor() { @@ -1169,9 +1193,8 @@ public class HttpOutputTest } private static String toUTF8String(Resource resource) - throws IOException { - return BufferUtil.toUTF8String(BufferUtil.toBuffer(resource, false)); + return BufferUtil.toUTF8String(IOResources.toRetainableByteBuffer(resource, null, false).getByteBuffer()); } interface ChainedInterceptor extends HttpOutput.Interceptor @@ -1192,6 +1215,7 @@ public class HttpOutputTest byte[] _arrayBuffer; InputStream _contentInputStream; ReadableByteChannel _contentChannel; + Resource _contentResource; ByteBuffer _content; ChainedInterceptor _interceptor; final FuturePromise _closedAfterWrite = new FuturePromise<>(); @@ -1227,6 +1251,14 @@ public class HttpOutputTest return; } + if (_contentResource != null) + { + out.sendContent(_contentResource); + _contentResource = null; + _closedAfterWrite.succeeded(out.isClosed()); + return; + } + if (_content != null && _writeLengthIfKnown) response.setContentLength(_content.remaining()); 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 51c892853ca..3824d5f5f6f 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 @@ -48,6 +48,7 @@ import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpTester; import org.eclipse.jetty.http.UriCompliance; import org.eclipse.jetty.http.content.ResourceHttpContent; +import org.eclipse.jetty.io.IOResources; import org.eclipse.jetty.logging.StacklessLogging; import org.eclipse.jetty.server.AllowedResourceAliasChecker; import org.eclipse.jetty.server.HttpConfiguration; @@ -59,7 +60,6 @@ import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; -import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.resource.FileSystemPool; import org.eclipse.jetty.util.resource.Resource; @@ -2339,7 +2339,7 @@ public class DefaultServletTest ResourceService resourceService = new ResourceService(); resourceService.setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain") { - final ByteBuffer buffer = BufferUtil.toBuffer(getResource(), false); + final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), null, false).getByteBuffer(); @Override public ByteBuffer getByteBuffer() @@ -2370,7 +2370,7 @@ public class DefaultServletTest ResourceService resourceService = new ResourceService(); resourceService.setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain") { - final ByteBuffer buffer = BufferUtil.toBuffer(getResource(), false); + final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), null, false).getByteBuffer(); @Override public ByteBuffer getByteBuffer() diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-common/src/main/java/org/eclipse/jetty/ee9/session/AbstractWebAppObjectInSessionTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-common/src/main/java/org/eclipse/jetty/ee9/session/AbstractWebAppObjectInSessionTest.java index 3c714c1c77a..c267bac06d4 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-common/src/main/java/org/eclipse/jetty/ee9/session/AbstractWebAppObjectInSessionTest.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-common/src/main/java/org/eclipse/jetty/ee9/session/AbstractWebAppObjectInSessionTest.java @@ -14,7 +14,6 @@ package org.eclipse.jetty.ee9.session; import java.io.File; -import java.io.FileOutputStream; import java.io.FileWriter; import jakarta.servlet.http.HttpServletResponse; @@ -29,7 +28,6 @@ import org.eclipse.jetty.session.DefaultSessionCacheFactory; import org.eclipse.jetty.session.SessionCache; import org.eclipse.jetty.session.SessionDataStoreFactory; import org.eclipse.jetty.session.test.AbstractSessionTestBase; -import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.ResourceFactory; import org.junit.jupiter.api.Test; @@ -87,14 +85,14 @@ public abstract class AbstractWebAppObjectInSessionTest extends AbstractSessionT //File sourceFile = new File(getClass().getClassLoader().getResource(resourceName).toURI()); File targetFile = new File(packageDirs, resourceName); //copy(sourceFile, targetFile); - IO.copy(resource.newInputStream(), new FileOutputStream(targetFile)); + resource.copyTo(targetFile.toPath()); resourceName = WebAppObjectInSessionServlet.class.getSimpleName() + "$" + WebAppObjectInSessionServlet.TestSharedStatic.class.getSimpleName() + ".class"; resource = resourceFactory.newResource(getClass().getResource(packageName + resourceName)); //sourceFile = new File(getClass().getClassLoader().getResource(resourceName).toURI()); targetFile = new File(packageDirs, resourceName); //copy(sourceFile, targetFile); - IO.copy(resource.newInputStream(), new FileOutputStream(targetFile)); + resource.copyTo(targetFile.toPath()); } DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); diff --git a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/Descriptor.java b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/Descriptor.java index 489f0e57aa4..6bae795684c 100644 --- a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/Descriptor.java +++ b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/Descriptor.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.Objects; +import org.eclipse.jetty.io.IOResources; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.xml.XmlParser; import org.slf4j.Logger; @@ -44,7 +45,7 @@ public abstract class Descriptor if (_root == null) { Objects.requireNonNull(parser); - try (InputStream is = _xml.newInputStream()) + try (InputStream is = IOResources.asInputStream(_xml)) { _root = parser.parse(is); _dtd = parser.getDTD();